diff --git a/bin/unittestschema/idandref.json b/bin/unittestschema/idandref.json new file mode 100644 index 0000000..ad485d2 Binary files /dev/null and b/bin/unittestschema/idandref.json differ diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 90e5903..b5e952b 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -16,6 +16,7 @@ #define RAPIDJSON_POINTER_H_ #include "document.h" +#include "uri.h" #include "internal/itoa.h" #ifdef __clang__ @@ -80,6 +81,8 @@ class GenericPointer { public: typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value typedef typename ValueType::Ch Ch; //!< Character type from Value + typedef GenericUri UriType; + //! A token is the basic units of internal representation. /*! @@ -520,6 +523,69 @@ public: //@} + //!@name Compute URI + //@{ + + //! Compute the in-scope URI for a subtree. + // For use with JSON pointers into JSON schema documents. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token. + \return Uri if it can be resolved. Otherwise null. + + \note + There are only 3 situations when a URI cannot be resolved: + 1. A value in the path is not an array nor object. + 2. An object value does not contain the token. + 3. A token is out of range of an array value. + + Use unresolvedTokenIndex to retrieve the token index. + */ + UriType GetUri(ValueType& root, const UriType& rootUri, size_t* unresolvedTokenIndex = 0) const { + static const Ch kIdString[] = { 'i', 'd', '\0' }; + static const ValueType kIdValue(kIdString, 2); + UriType base = rootUri; + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + switch (v->GetType()) { + case kObjectType: + { + // See if we have an id, and if so resolve with the current base + typename ValueType::MemberIterator m = v->FindMember(kIdValue); + if (m != v->MemberEnd() && (m->value).IsString()) { + UriType here = UriType(m->value); + here.Resolve(base); + base = here; + } + m = v->FindMember(GenericValue(GenericStringRef(t->name, t->length))); + if (m == v->MemberEnd()) + break; + v = &m->value; + } + continue; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + break; + v = &((*v)[t->index]); + continue; + default: + break; + } + + // Error: unresolved token + if (unresolvedTokenIndex) + *unresolvedTokenIndex = static_cast(t - tokens_); + return UriType(); + } + return base; + } + + UriType GetUri(const ValueType& root, const UriType& rootUri, size_t* unresolvedTokenIndex = 0) const { + return GetUri(const_cast(root), rootUri, unresolvedTokenIndex); + } + + //!@name Query value //@{ diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index e9ed19d..e290fc1 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -1,6 +1,7 @@ // Tencent is pleased to support the open source community by making RapidJSON available-> // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> +// Portions (C) Copyright IBM Corporation 2021 // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License-> You may obtain a copy of the License at @@ -19,6 +20,7 @@ #include "pointer.h" #include "stringbuffer.h" #include "error/en.h" +#include "uri.h" #include // abs, floor #if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) @@ -150,9 +152,6 @@ enum ValidateFlag { template class GenericSchemaDocument; -template -class Uri; - namespace internal { template @@ -435,7 +434,7 @@ public: typedef Schema SchemaType; typedef GenericValue SValue; typedef IValidationErrorHandler ErrorHandler; - typedef Uri UriType; + typedef GenericUri UriType; friend class GenericSchemaDocument; Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator, const UriType& id = UriType()) : @@ -496,7 +495,6 @@ public: // If we have an id property, resolve it with the in-scope id if (const ValueType* v = GetMember(value, GetIdString())) { if (v->IsString()) { - //std::cout << "Resolving local id '" << v->.GetString() << "' with in-scope id '" << id.GetString() << "'" << std::endl; UriType local = UriType(*v); local.Resolve(id_); id_ = local; @@ -727,7 +725,7 @@ public: return id_; } - const PointerType& GetPointer() const { + const PointerType& GetPointer() const { return pointer_; } @@ -806,7 +804,7 @@ public: foundEnum:; } - // Only check allOf etc if we have validators + // Only check allOf etc if we have validators if (context.validatorCount > 0) { if (allOf_.schemas) for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) @@ -1592,209 +1590,6 @@ struct TokenHelper { } // namespace internal -/////////////////////////////////////////////////////////////////////////////// -// Uri - -template -class Uri { -public: - typedef typename SchemaDocumentType::Ch Ch; - typedef typename SchemaDocumentType::AllocatorType AllocatorType; - typedef internal::Schema SchemaType; - typedef std::basic_string String; - - // Constructors - Uri() {} - - Uri(const String& uri) { - Parse(uri); - } - - Uri(const Ch* uri, SizeType len) { - Parse(String(uri, len)); - } - - // Use with specializations of GenericValue - template Uri(const T& uri) { - Parse(uri.template Get()); - } - - // Getters - const String& Get() { - // Create uri_ on-demand - if (uri_.empty()) uri_ = this->GetDoc() + frag_; - return uri_; } - - // Use with specializations of GenericValue - template void Get(T& uri, AllocatorType& allocator) { - uri.template Set(this->Get(), allocator); - } - - const String& GetDoc() { - // Create doc_ on-demand - if (doc_.empty()) doc_ = scheme_ + auth_ + path_ + query_; - return doc_; - } - const String& GetScheme() const { return scheme_; } - const String& GetAuth() const { return auth_; } - const String& GetPath() const { return path_; } - const String& GetQuery() const { return query_; } - const String& GetFrag() const { return frag_; } - - const Ch* GetString() { return this->Get().c_str(); } - SizeType GetStringLength() { return static_cast(this->Get().length()); } - - const Ch* GetDocString() { return this->GetDoc().c_str(); } - SizeType GetDocStringLength() { return static_cast(this->GetDoc().length()); } - - const Ch* GetFragString() const { return frag_.c_str(); } - SizeType GetFragStringLength() const { return static_cast(frag_.length()); } - - // Resolve this URI against a base URI in accordance with URI resolution rules at - // https://tools.ietf.org/html/rfc3986 - // Use for resolving an id or $ref with an in-scope id. - // This URI is updated in place where needed from the base URI. - Uri& Resolve(const Uri& base) - { - if (!scheme_.empty()) { - // Use all of this URI - RemoveDotSegments(path_); - } else { - if (!auth_.empty()) { - RemoveDotSegments(path_); - } else { - if (path_.empty()) { - path_ = base.GetPath(); - if (query_.empty()) { - query_ = base.GetQuery(); - } - } else { - static const String slash = SchemaType::GetSlashString().GetString(); - if (path_.find(slash) == 0) { - // Absolute path - replace all the path - RemoveDotSegments(path_); - } else { - // Relative path - append to path after last slash - String p; - if (!base.GetAuth().empty() && base.GetPath().empty()) p = slash; - std::size_t lastslashpos = base.GetPath().find_last_of(slash); - path_ = p + base.GetPath().substr(0, lastslashpos + 1) + path_; - RemoveDotSegments(path_); - } - } - auth_ = base.GetAuth(); - } - scheme_ = base.GetScheme(); - } - //std::cout << " Resolved uri: " << this->GetString() << std::endl; - return *this; - } - -private: - // Parse a URI into constituent scheme, authority, path, query, fragment - // Supports URIs that match regex ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? as per - // https://tools.ietf.org/html/rfc3986 - void Parse(const String& uri) { - std::size_t start = 0, pos1 = 0, pos2 = 0; - const std::size_t len = uri.length(); - static const String schemeEnd = SchemaType::GetSchemeEndString().GetString(); - static const String authStart = SchemaType::GetAuthStartString().GetString(); - static const String pathStart = SchemaType::GetSlashString().GetString(); - static const String queryStart = SchemaType::GetQueryStartString().GetString(); - static const String fragStart = SchemaType::GetFragStartString().GetString(); - // Look for scheme ([^:/?#]+):)? - if (start < len) { - pos1 = uri.find(schemeEnd); - if (pos1 != std::string::npos) { - pos2 = uri.find_first_of(pathStart + queryStart + fragStart); - if (pos1 < pos2) { - pos1 += schemeEnd.length(); - scheme_ = uri.substr(start, pos1); - start = pos1; - } - } - } - // Look for auth (//([^/?#]*))? - if (start < len) { - pos1 = uri.find(authStart, start); - if (pos1 == start) { - pos2 = uri.find_first_of(pathStart + queryStart + fragStart, start + authStart.length()); - auth_ = uri.substr(start, pos2 - start); - start = pos2; - } - } - // Look for path ([^?#]*) - if (start < len) { - pos2 = uri.find_first_of(queryStart + fragStart, start); - if (start != pos2) { - path_ = uri.substr(start, pos2 - start); - if (path_.find(pathStart) == 0) { // absolute path - normalize - RemoveDotSegments(path_); - } - start = pos2; - } - } - // Look for query (\?([^#]*))? - if (start < len) { - pos2 = uri.find(fragStart, start); - if (start != pos2) { - query_ = uri.substr(start, pos2 - start); - start = pos2; - } - } - // Look for fragment (#(.*))? - if (start < len) { - frag_ = uri.substr(start); - } - //std::cout << " Parsed uri: " << "s: " << scheme_.c_str() << " a: " << auth_.c_str() << " p: " << path_.c_str() << " q: " << query_.c_str() << " f: " << frag_.c_str() << std::endl; - } - - // Remove . and .. segments from a path - // https://tools.ietf.org/html/rfc3986 - void RemoveDotSegments(String& path) { - String temp = path; - path.clear(); - static const String slash = SchemaType::GetSlashString().GetString(); - static const String dot = SchemaType::GetDotString().GetString(); - std::size_t pos = 0; - // Loop through each path segment - while (pos != std::string::npos) { - //std::cout << "Temp: '" << temp.c_str() << "' Path: '" << path.c_str() << "'" << std::endl; - pos = temp.find_first_of(slash); - // Get next segment - String seg = temp.substr(0, pos); - if (seg == dot) { - // Discard . segment - } else if (seg == dot + dot) { - // Backup a .. segment - // We expect to find a previously added slash at the end or nothing - std::size_t pos1 = path.find_last_of(slash); - // Make sure we don't go beyond the start - if (pos1 != std::string::npos && pos1 != 0) { - // Find the next to last slash and back up to it - pos1 = path.find_last_of(slash, pos1 - 1); - path = path.substr(0, pos1 + 1); - } - } else { - // Copy segment and add slash if not at end - path += seg; - if (pos != std::string::npos) path += slash; - } - // Move to next segment if not at end - if (pos != std::string::npos) temp = temp.substr(pos + 1); - } - //std::cout << "Final Temp: '" << temp.c_str() << "' Final Path: '" << path.c_str() << "'" << std::endl; - } - - String uri_; // Created on-demand - String doc_; // Created on-demand - String scheme_; // Includes the : - String auth_; // Includes the // - String path_; // Absolute if starts with / - String query_; // Includes the ? - String frag_; // Includes the # -}; - /////////////////////////////////////////////////////////////////////////////// // IGenericRemoteSchemaDocumentProvider @@ -1802,10 +1597,12 @@ template class IGenericRemoteSchemaDocumentProvider { public: typedef typename SchemaDocumentType::Ch Ch; + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename SchemaDocumentType::AllocatorType AllocatorType; virtual ~IGenericRemoteSchemaDocumentProvider() {} virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; - virtual const SchemaDocumentType* GetRemoteDocument(Uri uri) { return GetRemoteDocument(uri.GetDocString(), uri.GetDocStringLength()); } + virtual const SchemaDocumentType* GetRemoteDocument(GenericUri uri) { return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); } }; /////////////////////////////////////////////////////////////////////////////// @@ -1831,7 +1628,7 @@ public: typedef internal::Schema SchemaType; typedef GenericPointer PointerType; typedef GenericValue SValue; - typedef Uri UriType; + typedef GenericUri UriType; friend class internal::Schema; template friend class GenericSchemaValidator; @@ -1863,20 +1660,20 @@ public: Ch noUri[1] = {0}; uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); - UriType baseId(uri_); + docId_ = UriType(uri_); typeless_ = static_cast(allocator_->Malloc(sizeof(SchemaType))); - new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, baseId); + new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, docId_); // Generate root schema, it will call CreateSchema() to create sub-schemas, // And call HandleRefSchema() if there are $ref. // PR #1393 use input pointer if supplied root_ = typeless_; if (pointer.GetTokenCount() == 0) { - CreateSchemaRecursive(&root_, pointer, document, document, baseId); + CreateSchemaRecursive(&root_, pointer, document, document, docId_); } else if (const ValueType* v = pointer.Get(document)) { - CreateSchema(&root_, pointer, *v, document, baseId); + CreateSchema(&root_, pointer, *v, document, docId_); } RAPIDJSON_ASSERT(root_ != 0); @@ -1894,7 +1691,8 @@ public: typeless_(rhs.typeless_), schemaMap_(std::move(rhs.schemaMap_)), schemaRef_(std::move(rhs.schemaRef_)), - uri_(std::move(rhs.uri_)) + uri_(std::move(rhs.uri_)), + docId_(rhs.docId_), { rhs.remoteProvider_ = 0; rhs.allocator_ = 0; @@ -1945,10 +1743,10 @@ private: // Changed by PR #1393 void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) { if (v.GetType() == kObjectType) { - CreateSchema(schema, pointer, v, document, id); + UriType newid = CreateSchema(schema, pointer, v, document, id); for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) - CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document, id); + CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document, newid); } else if (v.GetType() == kArrayType) for (SizeType i = 0; i < v.Size(); i++) @@ -1956,20 +1754,20 @@ private: } // Changed by PR #1393 - void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) { + const UriType& CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) { RAPIDJSON_ASSERT(pointer.IsValid()); if (v.IsObject()) { - if (const SchemaType* sc = GetSchema(pointer)) { + if (const SchemaType* sc = GetSchema(pointer)) { if (schema) *schema = sc; - //std::cout << "Using Schema with id " << sc->GetId().GetString() << std::endl; - AddSchemaRefs(const_cast(sc)); + AddSchemaRefs(const_cast(sc)); } else if (!HandleRefSchema(pointer, schema, v, document, id)) { - // The new schema adds itself and its $ref(s) to schemaMap_ + // The new schema constructor adds itself and its $ref(s) to schemaMap_ SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_, id); if (schema) *schema = s; + return s->GetId(); } } else { @@ -1977,61 +1775,95 @@ private: *schema = typeless_; AddSchemaRefs(typeless_); } + return id; } // Changed by PR #1393 + // TODO should this return a UriType& ? bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document, const UriType& id) { - //std::cout << "HandleRefSchema called with id " << id.GetString() << std::endl; typename ValueType::ConstMemberIterator itr = v.FindMember(SchemaType::GetRefString()); if (itr == v.MemberEnd()) return false; - // Resolve the source pointer to the $ref'ed schema (finally) + // Resolve the source pointer to the $ref'ed schema (finally) new (schemaRef_.template Push()) SchemaRefPtr(&source); if (itr->value.IsString()) { SizeType len = itr->value.GetStringLength(); if (len > 0) { - const Ch* s = itr->value.GetString(); - if (s[0] != '#') { // Remote reference - resolve $ref against the in-scope id + // First resolve $ref against the in-scope id + UriType scopeId = id; + UriType ref = UriType(itr->value); + ref.Resolve(scopeId); + // See if the resolved $ref minus the fragment matches a resolved id in this document + // Search from the root. Returns the subschema in the document and its absolute JSON pointer. + PointerType basePointer = PointerType(); + const ValueType *base = FindId(document, ref, basePointer, docId_, false); + if (!base) { + // Remote reference - call the remote document provider if (remoteProvider_) { - UriType ref = UriType(itr->value); - ref.Resolve(id); - //std::cout << "Resolved $ref '" << s << "' against in-scope id '" << id.GetString() << "' giving '" << ref.GetDocString() << "'" << std::endl; if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) { - // Create a pointer from the # onwards - const PointerType pointer(ref.GetFragString(), ref.GetFragStringLength(), allocator_); - if (pointer.IsValid()) { - if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { - if (schema) - *schema = sc; - AddSchemaRefs(const_cast(sc)); - return true; + const Ch* s = ref.GetFragString(); + len = ref.GetFragStringLength(); + if (len <= 1 || s[1] == '/') { + // JSON pointer fragment, absolute in the remote schema + const PointerType pointer(s, len, allocator_); + if (pointer.IsValid()) { + // Get the subschema + if (const SchemaType *sc = remoteDocument->GetSchema(pointer)) { + if (schema) + *schema = sc; + AddSchemaRefs(const_cast(sc)); + return true; + } } - } + } else { + // Plain name fragment, not allowed + } } } } else { // Local reference - if (len == 1 || s[1] == '/') { - // JSON pointer - const PointerType pointer(s, len, allocator_); - if (pointer.IsValid() && !IsCyclicRef(pointer)) { - if (const ValueType *nv = pointer.Get(document)) { - CreateSchema(schema, pointer, *nv, document, id); - return true; - } + const Ch* s = ref.GetFragString(); + len = ref.GetFragStringLength(); + if (len <= 1 || s[1] == '/') { + // JSON pointer fragment, relative to the resolved URI + const PointerType relPointer(s, len, allocator_); + if (relPointer.IsValid()) { + // Get the subschema + if (const ValueType *v = relPointer.Get(*base)) { + // Now get the absolute JSON pointer by adding relative to base + PointerType pointer(basePointer); + for (SizeType i = 0; i < relPointer.GetTokenCount(); i++) + pointer = pointer.Append(relPointer.GetTokens()[i], allocator_); + //GenericStringBuffer sb; + //pointer.StringifyUriFragment(sb); + if (pointer.IsValid() && !IsCyclicRef(pointer)) { + // Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there + // TODO: cache pointer <-> id mapping + scopeId = pointer.GetUri(document, docId_); + CreateSchema(schema, pointer, *v, document, scopeId); + return true; + } + } + } + } else { + // Plain name fragment, relative to the resolved URI + // See if the fragment matches an id in this document. + // Search from the base we just established. Returns the subschema in the document and its absolute JSON pointer. + PointerType pointer = PointerType(); + if (const ValueType *v = FindId(*base, ref, pointer, UriType(ref.GetBase()), true, basePointer)) { + if (v && !IsCyclicRef(pointer)) { + //GenericStringBuffer sb; + //pointer.StringifyUriFragment(sb); + // Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there + // TODO: cache pointer <-> id mapping + scopeId = pointer.GetUri(document, docId_); + CreateSchema(schema, pointer, *v, document, scopeId); + return true; + } + } } - } else { - // Internal reference to an id - const ValueType val(s, len); - PointerType pointer = PointerType(); - ValueType *nv = FindId(document, val, pointer); - if (nv && !IsCyclicRef(pointer)) { - CreateSchema(schema, pointer, *nv, document, id); - return true; - } - } } } } @@ -2043,36 +1875,46 @@ private: return true; } - //! Find the first subschema with 'id' string property matching the specified value. - // Return a pointer to the subschema and its JSON pointer. - ValueType* FindId(const ValueType& doc, const ValueType& findval, PointerType& resptr, const PointerType& here = PointerType()) const { + //! Find the first subschema with a resolved 'id' that matches the specified URI. + // If full specified use all URI else ignore fragment. + // If found, return a pointer to the subschema and its JSON pointer. + // TODO cache pointer <-> id mapping + ValueType* FindId(const ValueType& doc, const UriType& finduri, PointerType& resptr, const UriType& baseuri, bool full, const PointerType& here = PointerType()) const { SizeType i = 0; ValueType* resval = 0; - switch(doc.GetType()) { - case kObjectType: - for (typename ValueType::ConstMemberIterator m = doc.MemberBegin(); m != doc.MemberEnd(); ++m) { - if (m->name == SchemaType::GetIdString() && m->value.GetType() == kStringType && m->value == findval) { - // Found the 'id' with the value - resval = const_cast(&doc); - resptr = here; - } else if (m->value.GetType() == kObjectType || m->value.GetType() == kArrayType) { - resval = FindId(m->value, findval, resptr, here.Append(m->name.GetString(), m->name.GetStringLength(), allocator_)); - } - if (resval) break; + UriType tempuri = finduri; + UriType localuri = baseuri; + if (doc.GetType() == kObjectType) { + // Establish the base URI of this object + typename ValueType::ConstMemberIterator m = doc.FindMember(SchemaType::GetIdString()); + if (m != doc.MemberEnd() && m->value.GetType() == kStringType) { + localuri = UriType(m->value); + localuri.Resolve(baseuri); + } + // See if it matches + if (localuri.Match(finduri, full)) { + resval = const_cast(&doc); + resptr = here; + return resval; + } + // No match, continue looking + for (typename ValueType::ConstMemberIterator m = doc.MemberBegin(); m != doc.MemberEnd(); ++m) { + if (m->value.GetType() == kObjectType || m->value.GetType() == kArrayType) { + resval = FindId(m->value, finduri, resptr, localuri, full, here.Append(m->name.GetString(), m->name.GetStringLength(), allocator_)); } - return resval; - case kArrayType: - for (typename ValueType::ConstValueIterator v = doc.Begin(); v != doc.End(); ++v) { - if (v->GetType() == kObjectType || v->GetType() == kArrayType) { - resval = FindId(*v, findval, resptr, here.Append(i, allocator_)); - } - if (resval) break; - i++; + if (resval) break; + } + } else if (doc.GetType() == kArrayType) { + // Continue looking + for (typename ValueType::ConstValueIterator v = doc.Begin(); v != doc.End(); ++v) { + if (v->GetType() == kObjectType || v->GetType() == kArrayType) { + resval = FindId(*v, finduri, resptr, localuri, full, here.Append(i, allocator_)); } - return resval; - default: - return resval; + if (resval) break; + i++; + } } + return resval; } // Added by PR #1393 @@ -2119,6 +1961,7 @@ private: internal::Stack schemaMap_; // Stores created Pointer -> Schemas internal::Stack schemaRef_; // Stores Pointer(s) from $ref(s) until resolved SValue uri_; // Schema document URI + UriType docId_; }; //! GenericSchemaDocument using Value type. diff --git a/include/rapidjson/uri.h b/include/rapidjson/uri.h new file mode 100644 index 0000000..04bd92e --- /dev/null +++ b/include/rapidjson/uri.h @@ -0,0 +1,259 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_URI_H_ +#define RAPIDJSON_URI_H_ + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// GenericUri + +template +class GenericUri { +public: + typedef typename ValueType::Ch Ch; + typedef std::basic_string String; + + // Constructors + GenericUri() {} + + GenericUri(const String& uri) { + Parse(uri); + } + + GenericUri(const Ch* uri, SizeType len) { + Parse(String(uri, len)); + } + + // Use with specializations of GenericValue + template GenericUri(const T& uri) { + Parse(uri.template Get()); + } + + // Getters + const String& Get() const { return uri_; } + + // Use with specializations of GenericValue + template void Get(T& uri, Allocator& allocator) { + uri.template Set(this->Get(), allocator); + } + + const String& GetBase() const { return base_; } + const String& GetScheme() const { return scheme_; } + const String& GetAuth() const { return auth_; } + const String& GetPath() const { return path_; } + const String& GetQuery() const { return query_; } + const String& GetFrag() const { return frag_; } + + const Ch* GetString() const { return uri_.c_str(); } + SizeType GetStringLength() const { return static_cast(uri_.length()); } + + const Ch* GetBaseString() const { return base_.c_str(); } + SizeType GetBaseStringLength() const { return static_cast(base_.length()); } + + const Ch* GetFragString() const { return frag_.c_str(); } + SizeType GetFragStringLength() const { return static_cast(frag_.length()); } + + // Resolve this URI against another URI in accordance with URI resolution rules at + // https://tools.ietf.org/html/rfc3986 + // Use for resolving an id or $ref with an in-scope id. + // This URI is updated in place where needed from the base URI. + GenericUri& Resolve(const GenericUri& uri) { + if (!scheme_.empty()) { + // Use all of this URI + RemoveDotSegments(path_); + } else { + if (!auth_.empty()) { + RemoveDotSegments(path_); + } else { + if (path_.empty()) { + path_ = uri.GetPath(); + if (query_.empty()) { + query_ = uri.GetQuery(); + } + } else { + static const String slash = GetSlashString().GetString(); + if (path_.find(slash) == 0) { + // Absolute path - replace all the path + RemoveDotSegments(path_); + } else { + // Relative path - append to path after last slash + String p; + if (!uri.GetAuth().empty() && uri.GetPath().empty()) p = slash; + std::size_t lastslashpos = uri.GetPath().find_last_of(slash); + path_ = p + uri.GetPath().substr(0, lastslashpos + 1) + path_; + RemoveDotSegments(path_); + } + } + auth_ = uri.GetAuth(); + } + scheme_ = uri.GetScheme(); + } + base_ = scheme_ + auth_ + path_ + query_; + uri_ = base_ + frag_; + //std::cout << " Resolved uri: " << uri_ << std::endl; + return *this; + } + + bool Match(const GenericUri& uri, bool full) const { + if (full) + return uri_ == uri.Get(); + else + return base_ == uri.GetBase(); + } + + // Generate functions for string literal according to Ch +#define RAPIDJSON_STRING_(name, ...) \ + static const ValueType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const ValueType v(s, static_cast(sizeof(s) / sizeof(Ch) - 1));\ + return v;\ + } + + RAPIDJSON_STRING_(SchemeEnd, ':') + RAPIDJSON_STRING_(AuthStart, '/', '/') + RAPIDJSON_STRING_(QueryStart, '?') + RAPIDJSON_STRING_(FragStart, '#') + RAPIDJSON_STRING_(Slash, '/') + RAPIDJSON_STRING_(Dot, '.') + +#undef RAPIDJSON_STRING_ + +private: + // Parse a URI into constituent scheme, authority, path, query, fragment + // Supports URIs that match regex ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? as per + // https://tools.ietf.org/html/rfc3986 + void Parse(const String& uri) { + std::size_t start = 0, pos1 = 0, pos2 = 0; + const std::size_t len = uri.length(); + static const String schemeEnd = GetSchemeEndString().GetString(); + static const String authStart = GetAuthStartString().GetString(); + static const String pathStart = GetSlashString().GetString(); + static const String queryStart = GetQueryStartString().GetString(); + static const String fragStart = GetFragStartString().GetString(); + // Look for scheme ([^:/?#]+):)? + if (start < len) { + pos1 = uri.find(schemeEnd); + if (pos1 != std::string::npos) { + pos2 = uri.find_first_of(pathStart + queryStart + fragStart); + if (pos1 < pos2) { + pos1 += schemeEnd.length(); + scheme_ = uri.substr(start, pos1); + start = pos1; + } + } + } + // Look for auth (//([^/?#]*))? + if (start < len) { + pos1 = uri.find(authStart, start); + if (pos1 == start) { + pos2 = uri.find_first_of(pathStart + queryStart + fragStart, start + authStart.length()); + auth_ = uri.substr(start, pos2 - start); + start = pos2; + } + } + // Look for path ([^?#]*) + if (start < len) { + pos2 = uri.find_first_of(queryStart + fragStart, start); + if (start != pos2) { + path_ = uri.substr(start, pos2 - start); + if (path_.find(pathStart) == 0) { // absolute path - normalize + RemoveDotSegments(path_); + } + start = pos2; + } + } + // Look for query (\?([^#]*))? + if (start < len) { + pos2 = uri.find(fragStart, start); + if (start != pos2) { + query_ = uri.substr(start, pos2 - start); + start = pos2; + } + } + // Look for fragment (#(.*))? + if (start < len) { + frag_ = uri.substr(start); + } + base_ = scheme_ + auth_ + path_ + query_; + uri_ = base_ + frag_; + //std::cout << " Parsed uri: " << "s: " << scheme_.c_str() << " a: " << auth_.c_str() << " p: " << path_.c_str() << " q: " << query_.c_str() << " f: " << frag_.c_str() << std::endl; + } + + // Remove . and .. segments from a path + // https://tools.ietf.org/html/rfc3986 + void RemoveDotSegments(String& path) { + String temp = path; + path.clear(); + static const String slash = GetSlashString().GetString(); + static const String dot = GetDotString().GetString(); + std::size_t pos = 0; + // Loop through each path segment + while (pos != std::string::npos) { + //std::cout << "Temp: '" << temp.c_str() << "' Path: '" << path.c_str() << "'" << std::endl; + pos = temp.find_first_of(slash); + // Get next segment + String seg = temp.substr(0, pos); + if (seg == dot) { + // Discard . segment + } else if (seg == dot + dot) { + // Backup a .. segment + // We expect to find a previously added slash at the end or nothing + std::size_t pos1 = path.find_last_of(slash); + // Make sure we don't go beyond the start + if (pos1 != std::string::npos && pos1 != 0) { + // Find the next to last slash and back up to it + pos1 = path.find_last_of(slash, pos1 - 1); + path = path.substr(0, pos1 + 1); + } + } else { + // Copy segment and add slash if not at end + path += seg; + if (pos != std::string::npos) path += slash; + } + // Move to next segment if not at end + if (pos != std::string::npos) temp = temp.substr(pos + 1); + } + //std::cout << "Final Temp: '" << temp.c_str() << "' Final Path: '" << path.c_str() << "'" << std::endl; + } + + String uri_ = String(); // Full uri + String base_ = String(); // Everything except fragment + String scheme_ = String(); // Includes the : + String auth_ = String(); // Includes the // + String path_ = String(); // Absolute if starts with / + String query_ = String(); // Includes the ? + String frag_ = String(); // Includes the # +}; + +//! GenericUri for Value (UTF-8, default allocator). +typedef GenericUri Uri; + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_URI_H_ diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt index fc8803e..6439c36 100644 --- a/test/unittest/CMakeLists.txt +++ b/test/unittest/CMakeLists.txt @@ -26,6 +26,7 @@ set(UNITTEST_SOURCES stringbuffertest.cpp strtodtest.cpp unittest.cpp + uritest.cpp valuetest.cpp writertest.cpp) diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 4371803..c693b8f 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -648,6 +648,48 @@ TEST(Pointer, Create) { } } +static const char kJsonIds[] = "{\n" + " \"id\": \"/root/\"," + " \"foo\":[\"bar\", \"baz\", {\"id\": \"inarray\", \"child\": 1}],\n" + " \"int\" : 2,\n" + " \"str\" : \"val\",\n" + " \"obj\": {\"id\": \"inobj\", \"child\": 3},\n" + " \"jbo\": {\"id\": true, \"child\": 4}\n" + "}"; + + +TEST(Pointer, GetUri) { + typedef std::basic_string String; + Document d; + d.Parse(kJsonIds); + + String doc = String("http://doc"); + EXPECT_TRUE((Pointer("").GetUri(d, Pointer::UriType(doc)).Get()) == doc); + EXPECT_TRUE((Pointer("/foo").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/"); + EXPECT_TRUE((Pointer("/foo/0").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/"); + EXPECT_TRUE((Pointer("/foo/2").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/"); + EXPECT_TRUE((Pointer("/foo/2/child").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/inarray"); + EXPECT_TRUE((Pointer("/int").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/"); + EXPECT_TRUE((Pointer("/str").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/"); + EXPECT_TRUE((Pointer("/obj").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/"); + EXPECT_TRUE((Pointer("/obj/child").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/inobj"); + EXPECT_TRUE((Pointer("/jbo").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/"); + EXPECT_TRUE((Pointer("/jbo/child").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/"); // id not string + + size_t unresolvedTokenIndex; + EXPECT_TRUE((Pointer("/foo/3").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == ""); // Out of boundary + EXPECT_EQ(1u, unresolvedTokenIndex); + EXPECT_TRUE((Pointer("/foo/a").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == ""); // "/foo" is an array, cannot query by "a" + EXPECT_EQ(1u, unresolvedTokenIndex); + EXPECT_TRUE((Pointer("/foo/0/0").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == ""); // "/foo/0" is an string, cannot further query + EXPECT_EQ(2u, unresolvedTokenIndex); + EXPECT_TRUE((Pointer("/foo/0/a").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == ""); // "/foo/0" is an string, cannot further query + EXPECT_EQ(2u, unresolvedTokenIndex); + + Pointer::Token tokens[] = { { "foo ...", 3, kPointerInvalidIndex } }; + EXPECT_TRUE((Pointer(tokens, 1).GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/"); +} + TEST(Pointer, Get) { Document d; d.Parse(kJson); diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index a076389..1b25e2f 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -2300,7 +2300,7 @@ TEST(SchemaValidatingReader, Invalid) { Document e; e.Parse( "{ \"maxLength\": {" -" \"errorCode\": 6," + " \"errorCode\": 6," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 3, \"actual\": \"ABCD\"" "}}"); @@ -2578,6 +2578,37 @@ TEST(SchemaValidator, Ref_internal_id_and_schema_pointer) { kValidateDefaultFlags, SchemaValidatorType, PointerType); } +// Test that $refs are correctly resolved when intermediate multiple ids are present +// Includes $ref to a part of the document with a different in-scope id, which also contains $ref.. +TEST(SchemaValidator, Ref_internal_multiple_ids) { + typedef GenericSchemaDocument > SchemaDocumentType; + //RemoteSchemaDocumentProvider provider; + CrtAllocator allocator; + char* schema = ReadFile("unittestschema/idandref.json", allocator); + Document sd; + sd.Parse(schema); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocumentType s(sd, "http://xyz", 10/*, &provider*/); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + INVALIDATE_(s, "{\"PA1\": \"s\", \"PA2\": \"t\", \"PA3\": \"r\", \"PX1\": 1, \"PX2Y\": 2, \"PX3Z\": 3, \"PX4\": 4, \"PX5\": 5, \"PX6\": 6, \"PX7W\": 7, \"PX8N\": { \"NX\": 8}}", "#", "errors", "#", + "{ \"type\": [" + " {\"errorCode\": 20, \"instanceRef\": \"#/PA1\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"}," + " {\"errorCode\": 20, \"instanceRef\": \"#/PA2\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"}," + " {\"errorCode\": 20, \"instanceRef\": \"#/PA3\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"}," + " {\"errorCode\": 20, \"instanceRef\": \"#/PX1\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}," + " {\"errorCode\": 20, \"instanceRef\": \"#/PX2Y\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}," + " {\"errorCode\": 20, \"instanceRef\": \"#/PX3Z\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}," + " {\"errorCode\": 20, \"instanceRef\": \"#/PX4\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}," + " {\"errorCode\": 20, \"instanceRef\": \"#/PX5\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}," + " {\"errorCode\": 20, \"instanceRef\": \"#/PX6\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}," + " {\"errorCode\": 20, \"instanceRef\": \"#/PX7W\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}," + " {\"errorCode\": 20, \"instanceRef\": \"#/PX8N/NX\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}" + "]}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidatorType, PointerType); + CrtAllocator::Free(schema); +} + TEST(SchemaValidator, Ref_remote_issue1210) { class SchemaDocumentProvider : public IRemoteSchemaDocumentProvider { SchemaDocument** collection; @@ -2916,250 +2947,6 @@ TEST(SchemaValidator, Schema_UnknownError) { ASSERT_TRUE(SchemaValidator::SchemaType::GetValidateErrorKeyword(kValidateErrors).GetString() == std::string("null")); } -TEST(SchemaValidator, Uri_Parse) { - typedef std::basic_string String; - typedef Uri > > Uri; - MemoryPoolAllocator allocator; - - String s = "http://auth/path?query#frag"; - Value v; - v.SetString(s, allocator); - Uri u = Uri(v); - EXPECT_TRUE(u.GetScheme() == "http:"); - EXPECT_TRUE(u.GetAuth() == "//auth"); - EXPECT_TRUE(u.GetPath() == "/path"); - EXPECT_TRUE(u.GetDoc() == "http://auth/path?query"); - EXPECT_TRUE(u.GetQuery() == "?query"); - EXPECT_TRUE(u.GetFrag() == "#frag"); - Value w; - u.Get(w, allocator); - EXPECT_TRUE(*w.GetString() == *v.GetString()); - - s = "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"; - v.SetString(s, allocator); - u = Uri(v); - EXPECT_TRUE(u.GetScheme() == "urn:"); - EXPECT_TRUE(u.GetAuth() == ""); - EXPECT_TRUE(u.GetPath() == "uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"); - EXPECT_TRUE(u.GetDoc() == "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"); - EXPECT_TRUE(u.GetQuery() == ""); - EXPECT_TRUE(u.GetFrag() == ""); - u.Get(w, allocator); - EXPECT_TRUE(*w.GetString() == *v.GetString()); - - s = ""; - v.SetString(s, allocator); - u = Uri(v); - EXPECT_TRUE(u.GetScheme() == ""); - EXPECT_TRUE(u.GetAuth() == ""); - EXPECT_TRUE(u.GetPath() == ""); - EXPECT_TRUE(u.GetDoc() == ""); - EXPECT_TRUE(u.GetQuery() == ""); - EXPECT_TRUE(u.GetFrag() == ""); - - s = "http://auth/"; - v.SetString(s, allocator); - u = Uri(v); - EXPECT_TRUE(u.GetScheme() == "http:"); - EXPECT_TRUE(u.GetAuth() == "//auth"); - EXPECT_TRUE(u.GetPath() == "/"); - EXPECT_TRUE(u.GetDoc() == "http://auth/"); - EXPECT_TRUE(u.GetQuery() == ""); - EXPECT_TRUE(u.GetFrag() == ""); - - s = "/path/sub"; - u = Uri(s); - EXPECT_TRUE(u.GetScheme() == ""); - EXPECT_TRUE(u.GetAuth() == ""); - EXPECT_TRUE(u.GetPath() == "/path/sub"); - EXPECT_TRUE(u.GetDoc() == "/path/sub"); - EXPECT_TRUE(u.GetQuery() == ""); - EXPECT_TRUE(u.GetFrag() == ""); - - // absolute path gets normalized - s = "/path/../sub/"; - u = Uri(s); - EXPECT_TRUE(u.GetScheme() == ""); - EXPECT_TRUE(u.GetAuth() == ""); - EXPECT_TRUE(u.GetPath() == "/sub/"); - EXPECT_TRUE(u.GetDoc() == "/sub/"); - EXPECT_TRUE(u.GetQuery() == ""); - EXPECT_TRUE(u.GetFrag() == ""); - - // relative path does not - s = "path/../sub"; - u = Uri(s); - EXPECT_TRUE(u.GetScheme() == ""); - EXPECT_TRUE(u.GetAuth() == ""); - EXPECT_TRUE(u.GetPath() == "path/../sub"); - EXPECT_TRUE(u.GetDoc() == "path/../sub"); - EXPECT_TRUE(u.GetQuery() == ""); - EXPECT_TRUE(u.GetFrag() == ""); - - s = "http://auth#frag/stuff"; - u = Uri(s); - EXPECT_TRUE(u.GetScheme() == "http:"); - EXPECT_TRUE(u.GetAuth() == "//auth"); - EXPECT_TRUE(u.GetPath() == ""); - EXPECT_TRUE(u.GetDoc() == "http://auth"); - EXPECT_TRUE(u.GetQuery() == ""); - EXPECT_TRUE(u.GetFrag() == "#frag/stuff"); - EXPECT_TRUE(u.Get() == s); - - s = "#frag/stuff"; - u = Uri(s); - EXPECT_TRUE(u.GetScheme() == ""); - EXPECT_TRUE(u.GetAuth() == ""); - EXPECT_TRUE(u.GetPath() == ""); - EXPECT_TRUE(u.GetDoc() == ""); - EXPECT_TRUE(u.GetQuery() == ""); - EXPECT_TRUE(u.GetFrag() == "#frag/stuff"); - EXPECT_TRUE(u.Get() == s); - - Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'}; - u = Uri(c, 11); - EXPECT_TRUE(String(u.GetString()) == "#frag/stuff"); - EXPECT_TRUE(u.GetStringLength() == 11); - EXPECT_TRUE(String(u.GetDocString()) == ""); - EXPECT_TRUE(u.GetDocStringLength() == 0); - EXPECT_TRUE(String(u.GetFragString()) == "#frag/stuff"); - EXPECT_TRUE(u.GetFragStringLength() == 11); -} - -TEST(SchemaValidator, Uri_Resolve) { - typedef std::basic_string String; - typedef Uri > > Uri; - - // ref is full uri - Uri base = Uri(String("http://auth/path/#frag")); - Uri ref = Uri(String("http://newauth/newpath#newfrag")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag"); - - base = Uri(String("/path/#frag")); - ref = Uri(String("http://newauth/newpath#newfrag")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag"); - - // ref is alternate uri - base = Uri(String("http://auth/path/#frag")); - ref = Uri(String("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f")); - EXPECT_TRUE(ref.Resolve(base).Get() == "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"); - - // ref is absolute path - base = Uri(String("http://auth/path/#")); - ref = Uri(String("/newpath#newfrag")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newpath#newfrag"); - - // ref is relative path - base = Uri(String("http://auth/path/file.json#frag")); - ref = Uri(String("newfile.json#")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#"); - - base = Uri(String("http://auth/path/file.json#frag/stuff")); - ref = Uri(String("newfile.json#newfrag/newstuff")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#newfrag/newstuff"); - - base = Uri(String("file.json")); - ref = Uri(String("newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - - base = Uri(String("file.json")); - ref = Uri(String("./newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - - base = Uri(String("file.json")); - ref = Uri(String("parent/../newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - - base = Uri(String("file.json")); - ref = Uri(String("parent/./newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "parent/newfile.json"); - - base = Uri(String("file.json")); - ref = Uri(String("../../parent/.././newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - - base = Uri(String("http://auth")); - ref = Uri(String("newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newfile.json"); - - // ref is fragment - base = Uri(String("#frag/stuff")); - ref = Uri(String("#newfrag/newstuff")); - EXPECT_TRUE(ref.Resolve(base).Get() == "#newfrag/newstuff"); - - // test ref fragment always wins - base = Uri(String("/path#frag")); - ref = Uri(String("")); - EXPECT_TRUE(ref.Resolve(base).Get() == "/path"); - - // Examples from RFC3896 - base = Uri(String("http://a/b/c/d;p?q")); - ref = Uri(String("g:h")); - EXPECT_TRUE(ref.Resolve(base).Get() == "g:h"); - ref = Uri(String("g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g"); - ref = Uri(String("./g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g"); - ref = Uri(String("g/")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g/"); - ref = Uri(String("/g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("//g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://g"); - ref = Uri(String("?y")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?y"); - ref = Uri(String("g?y")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y"); - ref = Uri(String("#s")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q#s"); - ref = Uri(String("g#s")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s"); - ref = Uri(String("g?y#s")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y#s"); - ref = Uri(String(";x")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/;x"); - ref = Uri(String("g;x")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x"); - ref = Uri(String("g;x?y#s")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x?y#s"); - ref = Uri(String("")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q"); - ref = Uri(String(".")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/"); - ref = Uri(String("./")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/"); - ref = Uri(String("..")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/"); - ref = Uri(String("../")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/"); - ref = Uri(String("../g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/g"); - ref = Uri(String("../..")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/"); - ref = Uri(String("../../")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/"); - ref = Uri(String("../../g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("../../../g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("../../../../g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("/./g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("/../g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("g.")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g."); - ref = Uri(String(".g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/.g"); - ref = Uri(String("g..")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g.."); - ref = Uri(String("..g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/..g"); - ref = Uri(String("g#s/../x")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s/../x"); -} - #if defined(_MSC_VER) || defined(__clang__) RAPIDJSON_DIAG_POP #endif diff --git a/test/unittest/uritest.cpp b/test/unittest/uritest.cpp new file mode 100644 index 0000000..d8a78b8 --- /dev/null +++ b/test/unittest/uritest.cpp @@ -0,0 +1,278 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#define RAPIDJSON_SCHEMA_VERBOSE 0 +#define RAPIDJSON_HAS_STDSTRING 1 + +#include "unittest.h" +#include "rapidjson/document.h" +#include "rapidjson/uri.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(variadic-macros) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4822) // local class member function does not have a body +#endif + +using namespace rapidjson; + +TEST(Uri, Parse) { + typedef std::basic_string String; + typedef GenericUri > Uri; + MemoryPoolAllocator allocator; + + String s = "http://auth/path?query#frag"; + Value v; + v.SetString(s, allocator); + Uri u = Uri(v); + EXPECT_TRUE(u.GetScheme() == "http:"); + EXPECT_TRUE(u.GetAuth() == "//auth"); + EXPECT_TRUE(u.GetPath() == "/path"); + EXPECT_TRUE(u.GetBase() == "http://auth/path?query"); + EXPECT_TRUE(u.GetQuery() == "?query"); + EXPECT_TRUE(u.GetFrag() == "#frag"); + Value w; + u.Get(w, allocator); + EXPECT_TRUE(*w.GetString() == *v.GetString()); + + s = "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"; + v.SetString(s, allocator); + u = Uri(v); + EXPECT_TRUE(u.GetScheme() == "urn:"); + EXPECT_TRUE(u.GetAuth() == ""); + EXPECT_TRUE(u.GetPath() == "uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"); + EXPECT_TRUE(u.GetBase() == "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"); + EXPECT_TRUE(u.GetQuery() == ""); + EXPECT_TRUE(u.GetFrag() == ""); + u.Get(w, allocator); + EXPECT_TRUE(*w.GetString() == *v.GetString()); + + s = ""; + v.SetString(s, allocator); + u = Uri(v); + EXPECT_TRUE(u.GetScheme() == ""); + EXPECT_TRUE(u.GetAuth() == ""); + EXPECT_TRUE(u.GetPath() == ""); + EXPECT_TRUE(u.GetBase() == ""); + EXPECT_TRUE(u.GetQuery() == ""); + EXPECT_TRUE(u.GetFrag() == ""); + + s = "http://auth/"; + v.SetString(s, allocator); + u = Uri(v); + EXPECT_TRUE(u.GetScheme() == "http:"); + EXPECT_TRUE(u.GetAuth() == "//auth"); + EXPECT_TRUE(u.GetPath() == "/"); + EXPECT_TRUE(u.GetBase() == "http://auth/"); + EXPECT_TRUE(u.GetQuery() == ""); + EXPECT_TRUE(u.GetFrag() == ""); + + s = "/path/sub"; + u = Uri(s); + EXPECT_TRUE(u.GetScheme() == ""); + EXPECT_TRUE(u.GetAuth() == ""); + EXPECT_TRUE(u.GetPath() == "/path/sub"); + EXPECT_TRUE(u.GetBase() == "/path/sub"); + EXPECT_TRUE(u.GetQuery() == ""); + EXPECT_TRUE(u.GetFrag() == ""); + + // absolute path gets normalized + s = "/path/../sub/"; + u = Uri(s); + EXPECT_TRUE(u.GetScheme() == ""); + EXPECT_TRUE(u.GetAuth() == ""); + EXPECT_TRUE(u.GetPath() == "/sub/"); + EXPECT_TRUE(u.GetBase() == "/sub/"); + EXPECT_TRUE(u.GetQuery() == ""); + EXPECT_TRUE(u.GetFrag() == ""); + + // relative path does not + s = "path/../sub"; + u = Uri(s); + EXPECT_TRUE(u.GetScheme() == ""); + EXPECT_TRUE(u.GetAuth() == ""); + EXPECT_TRUE(u.GetPath() == "path/../sub"); + EXPECT_TRUE(u.GetBase() == "path/../sub"); + EXPECT_TRUE(u.GetQuery() == ""); + EXPECT_TRUE(u.GetFrag() == ""); + + s = "http://auth#frag/stuff"; + u = Uri(s); + EXPECT_TRUE(u.GetScheme() == "http:"); + EXPECT_TRUE(u.GetAuth() == "//auth"); + EXPECT_TRUE(u.GetPath() == ""); + EXPECT_TRUE(u.GetBase() == "http://auth"); + EXPECT_TRUE(u.GetQuery() == ""); + EXPECT_TRUE(u.GetFrag() == "#frag/stuff"); + EXPECT_TRUE(u.Get() == s); + + s = "#frag/stuff"; + u = Uri(s); + EXPECT_TRUE(u.GetScheme() == ""); + EXPECT_TRUE(u.GetAuth() == ""); + EXPECT_TRUE(u.GetPath() == ""); + EXPECT_TRUE(u.GetBase() == ""); + EXPECT_TRUE(u.GetQuery() == ""); + EXPECT_TRUE(u.GetFrag() == "#frag/stuff"); + EXPECT_TRUE(u.Get() == s); + + Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'}; + u = Uri(c, 11); + EXPECT_TRUE(String(u.GetString()) == "#frag/stuff"); + EXPECT_TRUE(u.GetStringLength() == 11); + EXPECT_TRUE(String(u.GetBaseString()) == ""); + EXPECT_TRUE(u.GetBaseStringLength() == 0); + EXPECT_TRUE(String(u.GetFragString()) == "#frag/stuff"); + EXPECT_TRUE(u.GetFragStringLength() == 11); +} + +TEST(Uri, Resolve) { + typedef std::basic_string String; + typedef GenericUri > Uri; + + // ref is full uri + Uri base = Uri(String("http://auth/path/#frag")); + Uri ref = Uri(String("http://newauth/newpath#newfrag")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag"); + + base = Uri(String("/path/#frag")); + ref = Uri(String("http://newauth/newpath#newfrag")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag"); + + // ref is alternate uri + base = Uri(String("http://auth/path/#frag")); + ref = Uri(String("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f")); + EXPECT_TRUE(ref.Resolve(base).Get() == "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"); + + // ref is absolute path + base = Uri(String("http://auth/path/#")); + ref = Uri(String("/newpath#newfrag")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newpath#newfrag"); + + // ref is relative path + base = Uri(String("http://auth/path/file.json#frag")); + ref = Uri(String("newfile.json#")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#"); + + base = Uri(String("http://auth/path/file.json#frag/stuff")); + ref = Uri(String("newfile.json#newfrag/newstuff")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#newfrag/newstuff"); + + base = Uri(String("file.json")); + ref = Uri(String("newfile.json")); + EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); + + base = Uri(String("file.json")); + ref = Uri(String("./newfile.json")); + EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); + + base = Uri(String("file.json")); + ref = Uri(String("parent/../newfile.json")); + EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); + + base = Uri(String("file.json")); + ref = Uri(String("parent/./newfile.json")); + EXPECT_TRUE(ref.Resolve(base).Get() == "parent/newfile.json"); + + base = Uri(String("file.json")); + ref = Uri(String("../../parent/.././newfile.json")); + EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); + + base = Uri(String("http://auth")); + ref = Uri(String("newfile.json")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newfile.json"); + + // ref is fragment + base = Uri(String("#frag/stuff")); + ref = Uri(String("#newfrag/newstuff")); + EXPECT_TRUE(ref.Resolve(base).Get() == "#newfrag/newstuff"); + + // test ref fragment always wins + base = Uri(String("/path#frag")); + ref = Uri(String("")); + EXPECT_TRUE(ref.Resolve(base).Get() == "/path"); + + // Examples from RFC3896 + base = Uri(String("http://a/b/c/d;p?q")); + ref = Uri(String("g:h")); + EXPECT_TRUE(ref.Resolve(base).Get() == "g:h"); + ref = Uri(String("g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g"); + ref = Uri(String("./g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g"); + ref = Uri(String("g/")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g/"); + ref = Uri(String("/g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); + ref = Uri(String("//g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://g"); + ref = Uri(String("?y")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?y"); + ref = Uri(String("g?y")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y"); + ref = Uri(String("#s")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q#s"); + ref = Uri(String("g#s")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s"); + ref = Uri(String("g?y#s")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y#s"); + ref = Uri(String(";x")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/;x"); + ref = Uri(String("g;x")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x"); + ref = Uri(String("g;x?y#s")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x?y#s"); + ref = Uri(String("")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q"); + ref = Uri(String(".")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/"); + ref = Uri(String("./")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/"); + ref = Uri(String("..")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/"); + ref = Uri(String("../")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/"); + ref = Uri(String("../g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/g"); + ref = Uri(String("../..")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/"); + ref = Uri(String("../../")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/"); + ref = Uri(String("../../g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); + ref = Uri(String("../../../g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); + ref = Uri(String("../../../../g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); + ref = Uri(String("/./g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); + ref = Uri(String("/../g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); + ref = Uri(String("g.")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g."); + ref = Uri(String(".g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/.g"); + ref = Uri(String("g..")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g.."); + ref = Uri(String("..g")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/..g"); + ref = Uri(String("g#s/../x")); + EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s/../x"); +} + +#if defined(_MSC_VER) || defined(__clang__) +RAPIDJSON_DIAG_POP +#endif