From 7698b3cd4868d4e649d2050a31b1c29e0bd1a562 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 25 Feb 2021 21:45:29 +0000 Subject: [PATCH 01/14] code and tests --- include/rapidjson/document.h | 2 +- include/rapidjson/schema.h | 486 +++++++++++++++++++++++------ test/unittest/schematest.cpp | 590 ++++++++++++++++++++++++++++++++++- 3 files changed, 981 insertions(+), 97 deletions(-) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 028235ec6..0b123ae0e 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -1967,7 +1967,7 @@ class GenericValue { case kArrayType: if (RAPIDJSON_UNLIKELY(!handler.StartArray())) return false; - for (const GenericValue* v = Begin(); v != End(); ++v) + for (ConstValueIterator v = Begin(); v != End(); ++v) if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) return false; return handler.EndArray(data_.a.size); diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 6576c250a..9e99bf5df 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -150,6 +150,9 @@ enum ValidateFlag { template class GenericSchemaDocument; +template +class Uri; + namespace internal { template @@ -432,11 +435,13 @@ class Schema { typedef Schema SchemaType; typedef GenericValue SValue; typedef IValidationErrorHandler ErrorHandler; + typedef Uri UriType; friend class GenericSchemaDocument; - Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : + Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator, const UriType& id = UriType()) : allocator_(allocator), uri_(schemaDocument->GetURI(), *allocator), + id_(id), pointer_(p, allocator), typeless_(schemaDocument->GetTypeless()), enum_(), @@ -474,9 +479,30 @@ class Schema { typedef typename ValueType::ConstValueIterator ConstValueIterator; typedef typename ValueType::ConstMemberIterator ConstMemberIterator; + // PR #1393 + // Early add this Schema and its $ref(s) in schemaDocument's map to avoid infinite + // recursion (with recursive schemas), since schemaDocument->getSchema() is always + // checked before creating a new one. Don't cache typeless_, though. + if (this != typeless_) { + typedef typename SchemaDocumentType::SchemaEntry SchemaEntry; + SchemaEntry *entry = schemaDocument->schemaMap_.template Push(); + new (entry) SchemaEntry(pointer_, this, true, allocator_); + schemaDocument->AddSchemaRefs(this); + } + if (!value.IsObject()) return; + // 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; + } + } + if (const ValueType* v = GetMember(value, GetTypeString())) { type_ = 0; if (v->IsString()) @@ -507,7 +533,7 @@ class Schema { } if (const ValueType* v = GetMember(value, GetNotString())) { - schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document); + schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document, id_); notValidatorIndex_ = validatorCount_; validatorCount_++; } @@ -524,7 +550,7 @@ class Schema { if (properties && properties->IsObject()) for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) AddUniqueElement(allProperties, itr->name); - + if (required && required->IsArray()) for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) if (itr->IsString()) @@ -555,7 +581,7 @@ class Schema { for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { SizeType index; if (FindPropertyIndex(itr->name, &index)) - schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); + schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document, id_); } } @@ -567,7 +593,7 @@ class Schema { for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { new (&patternProperties_[patternPropertyCount_]) PatternProperty(); patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); - schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); + schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document, id_); patternPropertyCount_++; } } @@ -599,7 +625,7 @@ class Schema { } else if (itr->value.IsObject()) { hasSchemaDependencies_ = true; - schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); + schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document, id_); properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; validatorCount_++; } @@ -611,7 +637,7 @@ class Schema { if (v->IsBool()) additionalProperties_ = v->GetBool(); else if (v->IsObject()) - schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); + schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document, id_); } AssignIfExist(minProperties_, value, GetMinPropertiesString()); @@ -621,12 +647,12 @@ class Schema { if (const ValueType* v = GetMember(value, GetItemsString())) { PointerType q = p.Append(GetItemsString(), allocator_); if (v->IsObject()) // List validation - schemaDocument->CreateSchema(&itemsList_, q, *v, document); + schemaDocument->CreateSchema(&itemsList_, q, *v, document, id_); else if (v->IsArray()) { // Tuple validation itemsTuple_ = static_cast(allocator_->Malloc(sizeof(const Schema*) * v->Size())); SizeType index = 0; for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) - schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); + schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document, id_); } } @@ -637,7 +663,7 @@ class Schema { if (v->IsBool()) additionalItems_ = v->GetBool(); else if (v->IsObject()) - schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); + schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document, id_); } AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); @@ -697,7 +723,11 @@ class Schema { return uri_; } - const PointerType& GetPointer() const { + const UriType& GetId() const { + return id_; + } + + const PointerType& GetPointer() const { return pointer_; } @@ -826,7 +856,7 @@ class Schema { } return CreateParallelValidator(context); } - + bool Bool(Context& context, bool) const { if (!(type_ & (1 << kBooleanSchemaType))) { DisallowedType(context, GetBooleanString()); @@ -870,13 +900,13 @@ class Schema { if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) return false; - + if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) return false; - + return CreateParallelValidator(context); } - + bool String(Context& context, const Ch* str, SizeType length, bool) const { if (!(type_ & (1 << kStringSchemaType))) { DisallowedType(context, GetStringString()); @@ -925,7 +955,7 @@ class Schema { return CreateParallelValidator(context); } - + bool Key(Context& context, const Ch* str, SizeType len, bool) const { if (patternProperties_) { context.patternPropertiesSchemaCount = 0; @@ -1018,7 +1048,7 @@ class Schema { } } if (context.error_handler.EndDependencyErrors()) - RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorDependencies); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorDependencies); } return true; @@ -1038,12 +1068,12 @@ class Schema { bool EndArray(Context& context, SizeType elementCount) const { context.inArray = false; - + if (elementCount < minItems_) { context.error_handler.TooFewItems(elementCount, minItems_); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinItems); } - + if (elementCount > maxItems_) { context.error_handler.TooManyItems(elementCount, maxItems_); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxItems); @@ -1132,6 +1162,15 @@ class Schema { RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't') + RAPIDJSON_STRING_(Ref, '$', 'r', 'e', 'f') + RAPIDJSON_STRING_(Id, 'i', 'd') + + RAPIDJSON_STRING_(SchemeEnd, ':') + RAPIDJSON_STRING_(AuthStart, '/', '/') + RAPIDJSON_STRING_(QueryStart, '?') + RAPIDJSON_STRING_(FragStart, '#') + RAPIDJSON_STRING_(Slash, '/') + RAPIDJSON_STRING_(Dot, '.') #undef RAPIDJSON_STRING_ @@ -1197,7 +1236,7 @@ class Schema { out.schemas = static_cast(allocator_->Malloc(out.count * sizeof(const Schema*))); memset(out.schemas, 0, sizeof(Schema*)* out.count); for (SizeType i = 0; i < out.count; i++) - schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); + schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document, id_); out.begin = validatorCount_; validatorCount_ += out.count; } @@ -1274,10 +1313,10 @@ class Schema { if (anyOf_.schemas) CreateSchemaValidators(context, anyOf_, false); - + if (oneOf_.schemas) CreateSchemaValidators(context, oneOf_, false); - + if (not_) context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_, false); @@ -1301,7 +1340,7 @@ class Schema { SizeType len = name.GetStringLength(); const Ch* str = name.GetString(); for (SizeType index = 0; index < propertyCount_; index++) - if (properties_[index].name.GetStringLength() == len && + if (properties_[index].name.GetStringLength() == len && (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0)) { *outIndex = index; @@ -1462,7 +1501,7 @@ class Schema { struct PatternProperty { PatternProperty() : schema(), pattern() {} - ~PatternProperty() { + ~PatternProperty() { if (pattern) { pattern->~RegexType(); AllocatorType::Free(pattern); @@ -1474,6 +1513,7 @@ class Schema { AllocatorType* allocator_; SValue uri_; + UriType id_; PointerType pointer_; const SchemaType* typeless_; uint64_t* enum_; @@ -1516,7 +1556,7 @@ class Schema { SValue multipleOf_; bool exclusiveMinimum_; bool exclusiveMaximum_; - + SizeType defaultValueLength_; }; @@ -1552,6 +1592,209 @@ 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 @@ -1562,6 +1805,7 @@ class IGenericRemoteSchemaDocumentProvider { virtual ~IGenericRemoteSchemaDocumentProvider() {} virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; + virtual const SchemaDocumentType* GetRemoteDocument(Uri uri) { return GetRemoteDocument(uri.GetDocString(), uri.GetDocStringLength()); } }; /////////////////////////////////////////////////////////////////////////////// @@ -1586,7 +1830,8 @@ class GenericSchemaDocument { typedef typename EncodingType::Ch Ch; typedef internal::Schema SchemaType; typedef GenericPointer PointerType; - typedef GenericValue URIType; + typedef GenericValue SValue; + typedef Uri UriType; friend class internal::Schema; template friend class GenericSchemaValidator; @@ -1600,9 +1845,11 @@ class GenericSchemaDocument { \param uriLength Length of \c name, in code points. \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. \param allocator An optional allocator instance for allocating memory. Can be null. + \param pointer An optional JSON pointer to the start of the schema document */ explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, - IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : + IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0, + const PointerType& pointer = PointerType() : // PR #1393 remoteProvider_(remoteProvider), allocator_(allocator), ownAllocator_(), @@ -1616,30 +1863,20 @@ class GenericSchemaDocument { Ch noUri[1] = {0}; uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); + UriType baseId(uri_); typeless_ = static_cast(allocator_->Malloc(sizeof(SchemaType))); - new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_); + new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, baseId); // Generate root schema, it will call CreateSchema() to create sub-schemas, - // And call AddRefSchema() if there are $ref. - CreateSchemaRecursive(&root_, PointerType(), document, document); - - // Resolve $ref - while (!schemaRef_.Empty()) { - SchemaRefEntry* refEntry = schemaRef_.template Pop(1); - if (const SchemaType* s = GetSchema(refEntry->target)) { - if (refEntry->schema) - *refEntry->schema = s; - - // Create entry in map if not exist - if (!GetSchema(refEntry->source)) { - new (schemaMap_.template Push()) SchemaEntry(refEntry->source, const_cast(s), false, allocator_); - } - } - else if (refEntry->schema) - *refEntry->schema = typeless_; - - refEntry->~SchemaRefEntry(); + // 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); + } + else if (const ValueType* v = pointer.Get(document)) { + CreateSchema(&root_, pointer, *v, document, baseId); } RAPIDJSON_ASSERT(root_ != 0); @@ -1679,7 +1916,7 @@ class GenericSchemaDocument { RAPIDJSON_DELETE(ownAllocator_); } - const URIType& GetURI() const { return uri_; } + const SValue& GetURI() const { return uri_; } //! Get the root schema. const SchemaType& GetRoot() const { return *root_; } @@ -1690,12 +1927,7 @@ class GenericSchemaDocument { //! Prohibit assignment GenericSchemaDocument& operator=(const GenericSchemaDocument&); - struct SchemaRefEntry { - SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {} - PointerType source; - PointerType target; - const SchemaType** schema; - }; + typedef const PointerType* SchemaRefPtr; // PR #1393 struct SchemaEntry { SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} @@ -1710,79 +1942,153 @@ class GenericSchemaDocument { bool owned; }; - void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { - if (schema) - *schema = typeless_; - + // 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) { - const SchemaType* s = GetSchema(pointer); - if (!s) - CreateSchema(schema, pointer, v, document); + 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); + CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document, id); } else if (v.GetType() == kArrayType) for (SizeType i = 0; i < v.Size(); i++) - CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); + CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document, id); } - void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + // Changed by PR #1393 + void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) { RAPIDJSON_ASSERT(pointer.IsValid()); if (v.IsObject()) { - if (!HandleRefSchema(pointer, schema, v, document)) { - SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); - new (schemaMap_.template Push()) SchemaEntry(pointer, s, true, allocator_); + 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)); + } + else if (!HandleRefSchema(pointer, schema, v, document, id)) { + // The new schema 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; } } + else { + if (schema) + *schema = typeless_; + AddSchemaRefs(typeless_); + } } - bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { - static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; - static const ValueType kRefValue(kRefString, 4); - - typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue); + // Changed by PR #1393 + 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) + new (schemaRef_.template Push()) SchemaRefPtr(&source); + if (itr->value.IsString()) { SizeType len = itr->value.GetStringLength(); if (len > 0) { const Ch* s = itr->value.GetString(); - SizeType i = 0; - while (i < len && s[i] != '#') // Find the first # - i++; - - if (i > 0) { // Remote reference, resolve immediately + if (s[0] != '#') { // Remote reference - resolve $ref against the in-scope id if (remoteProvider_) { - if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) { - PointerType pointer(&s[i], len - i, allocator_); + 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; - new (schemaMap_.template Push()) SchemaEntry(source, const_cast(sc), false, allocator_); + AddSchemaRefs(const_cast(sc)); return true; } } } } } - else if (s[i] == '#') { // Local reference, defer resolution - PointerType pointer(&s[i], len - i, allocator_); - if (pointer.IsValid()) { - if (const ValueType* nv = pointer.Get(document)) - if (HandleRefSchema(source, schema, *nv, document)) - return true; - - new (schemaRef_.template Push()) SchemaRefEntry(source, pointer, schema, allocator_); + 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; + } } + } 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; + } + } } } } + + // Invalid/Unknown $ref + if (schema) + *schema = typeless_; + AddSchemaRefs(typeless_); + 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 { + 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; + } + 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++; + } + return resval; + default: + return resval; + } + } + + // Added by PR #1393 + void AddSchemaRefs(SchemaType* schema) { + while (!schemaRef_.Empty()) { + SchemaRefPtr *ref = schemaRef_.template Pop(1); + SchemaEntry *entry = schemaMap_.template Push(); + new (entry) SchemaEntry(**ref, schema, false, allocator_); + } + } + + // Added by PR #1393 + bool IsCyclicRef(const PointerType& pointer) const { + for (const SchemaRefPtr* ref = schemaRef_.template Bottom(); ref != schemaRef_.template End(); ++ref) + if (pointer == **ref) + return true; return false; } @@ -1811,8 +2117,8 @@ class GenericSchemaDocument { const SchemaType* root_; //!< Root schema. SchemaType* typeless_; internal::Stack schemaMap_; // Stores created Pointer -> Schemas - internal::Stack schemaRef_; // Stores Pointer from $ref and schema which holds the $ref - URIType uri_; + internal::Stack schemaRef_; // Stores Pointer(s) from $ref(s) until resolved + SValue uri_; }; //! GenericSchemaDocument using Value type. diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index f381b4e91..b3b929195 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -13,6 +13,7 @@ // specific language governing permissions and limitations under the License. #define RAPIDJSON_SCHEMA_VERBOSE 0 +#define RAPIDJSON_HAS_STDSTRING 1 #include "unittest.h" #include "rapidjson/schema.h" @@ -1811,6 +1812,189 @@ TEST(SchemaValidator, EscapedPointer) { "}}"); } +TEST(SchemaValidator, SchemaPointer) { + Document sd; + sd.Parse( + "{" + " \"swagger\": \"2.0\"," + " \"paths\": {" + " \"/some/path\": {" + " \"post\": {" + " \"parameters\": [" + " {" + " \"in\": \"body\"," + " \"name\": \"body\"," + " \"schema\": {" + " \"properties\": {" + " \"a\": {" + " \"$ref\": \"#/definitions/Prop_a\"" + " }," + " \"b\": {" + " \"type\": \"integer\"" + " }" + " }," + " \"type\": \"object\"" + " }" + " }" + " ]," + " \"responses\": {" + " \"200\": {" + " \"schema\": {" + " \"$ref\": \"#/definitions/Resp_200\"" + " }" + " }" + " }" + " }" + " }" + " }," + " \"definitions\": {" + " \"Prop_a\": {" + " \"properties\": {" + " \"c\": {" + " \"enum\": [" + " \"C1\"," + " \"C2\"," + " \"C3\"" + " ]," + " \"type\": \"string\"" + " }," + " \"d\": {" + " \"$ref\": \"#/definitions/Prop_d\"" + " }," + " \"s\": {" + " \"type\": \"string\"" + " }" + " }," + " \"required\": [\"c\"]," + " \"type\": \"object\"" + " }," + " \"Prop_d\": {" + " \"properties\": {" + " \"a\": {" + " \"$ref\": \"#/definitions/Prop_a\"" + " }," + " \"c\": {" + " \"$ref\": \"#/definitions/Prop_a/properties/c\"" + " }" + " }," + " \"type\": \"object\"" + " }," + " \"Resp_200\": {" + " \"properties\": {" + " \"e\": {" + " \"type\": \"string\"" + " }," + " \"f\": {" + " \"type\": \"boolean\"" + " }," + " \"cyclic_source\": {" + " \"$ref\": \"#/definitions/Resp_200/properties/cyclic_target\"" + " }," + " \"cyclic_target\": {" + " \"$ref\": \"#/definitions/Resp_200/properties/cyclic_source\"" + " }" + " }," + " \"type\": \"object\"" + " }" + " }" + "}"); + SchemaDocument s1(sd, NULL, 0, NULL, NULL, Pointer("#/paths/~1some~1path/post/parameters/0/schema")); + VALIDATE(s1, + "{" + " \"a\": {" + " \"c\": \"C1\"," + " \"d\": {" + " \"a\": {" + " \"c\": \"C2\"" + " }," + " \"c\": \"C3\"" + " }" + " }," + " \"b\": 123" + "}", + true); + INVALIDATE(s1, + "{" + " \"a\": {" + " \"c\": \"C1\"," + " \"d\": {" + " \"a\": {" + " \"c\": \"C2\"" + " }," + " \"c\": \"C3\"" + " }" + " }," + " \"b\": \"should be an int\"" + "}", + "#/paths/~1some~1path/post/parameters/0/schema/properties/b", "type", "#/b", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\":\"#/b\"," + " \"schemaRef\":\"#/paths/~1some~1path/post/parameters/0/schema/properties/b\"," + " \"expected\": [\"integer\"], \"actual\":\"string\"" + "}}"); + INVALIDATE(s1, + "{" + " \"a\": {" + " \"c\": \"C1\"," + " \"d\": {" + " \"a\": {" + " \"c\": \"should be within enum\"" + " }," + " \"c\": \"C3\"" + " }" + " }," + " \"b\": 123" + "}", + "#/definitions/Prop_a/properties/c", "enum", "#/a/d/a/c", + "{ \"enum\": {" + " \"errorCode\": 19," + " \"instanceRef\":\"#/a/d/a/c\"," + " \"schemaRef\":\"#/definitions/Prop_a/properties/c\"" + "}}"); + INVALIDATE(s1, + "{" + " \"a\": {" + " \"c\": \"C1\"," + " \"d\": {" + " \"a\": {" + " \"s\": \"required 'c' is missing\"" + " }" + " }" + " }," + " \"b\": 123" + "}", + "#/definitions/Prop_a", "required", "#/a/d/a", + "{ \"required\": {" + " \"errorCode\": 15," + " \"missing\":[\"c\"]," + " \"instanceRef\":\"#/a/d/a\"," + " \"schemaRef\":\"#/definitions/Prop_a\"" + "}}"); + SchemaDocument s2(sd, NULL, 0, NULL, NULL, Pointer("#/paths/~1some~1path/post/responses/200/schema")); + VALIDATE(s2, + "{ \"e\": \"some string\", \"f\": false }", + true); + INVALIDATE(s2, + "{ \"e\": true, \"f\": false }", + "#/definitions/Resp_200/properties/e", "type", "#/e", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\":\"#/e\"," + " \"schemaRef\":\"#/definitions/Resp_200/properties/e\"," + " \"expected\": [\"string\"], \"actual\":\"boolean\"" + "}}"); + INVALIDATE(s2, + "{ \"e\": \"some string\", \"f\": 123 }", + "#/definitions/Resp_200/properties/f", "type", "#/f", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\":\"#/f\"," + " \"schemaRef\":\"#/definitions/Resp_200/properties/f\"," + " \"expected\": [\"boolean\"], \"actual\":\"integer\"" + "}}"); +} + template static char* ReadFile(const char* filename, Allocator& allocator) { const char *paths[] = { @@ -1952,7 +2136,7 @@ class RemoteSchemaDocumentProvider : public IGenericRemoteSchemaDocumentProvider virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) { for (size_t i = 0; i < kCount; i++) - if (typename SchemaDocumentType::URIType(uri, length) == sd_[i]->GetURI()) + if (typename SchemaDocumentType::SType(uri, length) == sd_[i]->GetURI()) return sd_[i]; return 0; } @@ -2032,7 +2216,7 @@ TEST(SchemaValidator, TestSuite) { ADD_FAILURE(); } else { - //printf("json test suite file %s parsed ok\n", filename); + //printf("\njson test suite file %s parsed ok\n", filename); GenericDocument, MemoryPoolAllocator<>, MemoryPoolAllocator<> > d(&documentAllocator, 1024, &documentStackAllocator); d.Parse(json); if (d.HasParseError()) { @@ -2042,12 +2226,14 @@ TEST(SchemaValidator, TestSuite) { else { for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) { { + const char* description1 = (*schemaItr)["description"].GetString(); + //printf("\ncompiling schema for json test %s \n", description1); SchemaDocumentType schema((*schemaItr)["schema"], filenames[i], static_cast(strlen(filenames[i])), &provider, &schemaAllocator); GenericSchemaValidator >, MemoryPoolAllocator<> > validator(schema, &validatorAllocator); - const char* description1 = (*schemaItr)["description"].GetString(); const Value& tests = (*schemaItr)["tests"]; for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) { const char* description2 = (*testItr)["description"].GetString(); + //printf("running json test %s \n", description2); if (!onlyRunDescription || strcmp(description2, onlyRunDescription) == 0) { const Value& data = (*testItr)["data"]; bool expected = (*testItr)["valid"].GetBool(); @@ -2075,8 +2261,8 @@ TEST(SchemaValidator, TestSuite) { jsonAllocator.Clear(); } printf("%d / %d passed (%2d%%)\n", passCount, testCount, passCount * 100 / testCount); -// if (passCount != testCount) -// ADD_FAILURE(); + if (passCount != testCount) + ADD_FAILURE(); } TEST(SchemaValidatingReader, Simple) { @@ -2244,6 +2430,154 @@ TEST(SchemaValidator, Ref_remote) { kValidateDefaultFlags, SchemaValidatorType, PointerType); } +// Merge with id where $ref is full URI +TEST(SchemaValidator, Ref_remote_change_resolution_scope_uri) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + Document sd; + sd.Parse("{\"id\": \"http://ignore/blah#/ref\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"http://localhost:1234/subSchemas.json#/integer\"}}}"); + SchemaDocumentType s(sd, 0, 0, &provider); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#/myInt\"," + " \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\"," + " \"expected\": [\"integer\"], \"actual\": \"null\"" + "}}", + kValidateDefaultFlags, SchemaValidatorType, PointerType); +} + +// Merge with id where $ref is a relative path +TEST(SchemaValidator, Ref_remote_change_resolution_scope_relative_path) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + Document sd; + sd.Parse("{\"id\": \"http://localhost:1234/\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"subSchemas.json#/integer\"}}}"); + SchemaDocumentType s(sd, 0, 0, &provider); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#/myInt\"," + " \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\"," + " \"expected\": [\"integer\"], \"actual\": \"null\"" + "}}", + kValidateDefaultFlags, SchemaValidatorType, PointerType); +} + +// Merge with id where $ref is an absolute path +TEST(SchemaValidator, Ref_remote_change_resolution_scope_absolute_path) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + Document sd; + sd.Parse("{\"id\": \"http://localhost:1234/xxxx\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/integer\"}}}"); + SchemaDocumentType s(sd, 0, 0, &provider); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#/myInt\"," + " \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\"," + " \"expected\": [\"integer\"], \"actual\": \"null\"" + "}}", + kValidateDefaultFlags, SchemaValidatorType, PointerType); +} + +// Merge with id where $ref is an absolute path, and the document has a base URI +TEST(SchemaValidator, Ref_remote_change_resolution_scope_absolute_path_document) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/integer\"}}}"); + SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#/myInt\"," + " \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\"," + " \"expected\": [\"integer\"], \"actual\": \"null\"" + "}}", + kValidateDefaultFlags, SchemaValidatorType, PointerType); +} + +// $ref is a non-JSON pointer fragment and there a matching id +TEST(SchemaValidator, Ref_internal_id_1) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myStrId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}}}"); + SchemaDocumentType s(sd); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2", "type", "/myInt1", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#/myInt1\"," + " \"schemaRef\": \"#/properties/myInt2\"," + " \"expected\": [\"integer\"], \"actual\": \"null\"" + "}}", + kValidateDefaultFlags, SchemaValidatorType, PointerType); +} + +// $ref is a non-JSON pointer fragment and there are two matching ids so we take the first +TEST(SchemaValidator, Ref_internal_id_2) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myId\"}}}"); + SchemaDocumentType s(sd); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2", "type", "/myInt1", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#/myInt1\"," + " \"schemaRef\": \"#/properties/myInt2\"," + " \"expected\": [\"integer\"], \"actual\": \"null\"" + "}}", + kValidateDefaultFlags, SchemaValidatorType, PointerType); +} + +// $ref is a non-JSON pointer fragment and there is a matching id within array +TEST(SchemaValidator, Ref_internal_id_in_array) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"anyOf\": [{\"type\": \"string\", \"id\": \"#myStrId\"}, {\"type\": \"integer\", \"id\": \"#myId\"}]}}}"); + SchemaDocumentType s(sd); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2/anyOf/1", "type", "/myInt1", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#/myInt1\"," + " \"schemaRef\": \"#/properties/myInt2/anyOf/1\"," + " \"expected\": [\"integer\"], \"actual\": \"null\"" + "}}", + kValidateDefaultFlags, SchemaValidatorType, PointerType); +} + +// $ref is a non-JSON pointer fragment and there is a matching id, and the schema is embedded in the document +TEST(SchemaValidator, Ref_internal_id_and_schema_pointer) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{ \"schema\": {\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"anyOf\": [{\"type\": \"integer\", \"id\": \"#myId\"}]}}}}"); + typedef GenericPointer > PointerType; + SchemaDocumentType s(sd, 0, 0, 0, 0, PointerType("/schema")); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + INVALIDATE_(s, "{\"myInt1\": null}", "/schema/properties/myInt2/anyOf/0", "type", "/myInt1", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#/myInt1\"," + " \"schemaRef\": \"#/schema/properties/myInt2/anyOf/0\"," + " \"expected\": [\"integer\"], \"actual\": \"null\"" + "}}", + kValidateDefaultFlags, SchemaValidatorType, PointerType); +} + TEST(SchemaValidator, Ref_remote_issue1210) { class SchemaDocumentProvider : public IRemoteSchemaDocumentProvider { SchemaDocument** collection; @@ -2260,7 +2594,7 @@ TEST(SchemaValidator, Ref_remote_issue1210) { SchemaDocumentProvider(SchemaDocument** collection) : collection(collection) { } virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeType length) { int i = 0; - while (collection[i] && SchemaDocument::URIType(uri, length) != collection[i]->GetURI()) ++i; + while (collection[i] && SchemaDocument::SValue(uri, length) != collection[i]->GetURI()) ++i; return collection[i]; } }; @@ -2582,6 +2916,250 @@ 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 From 892f6e3fd361d8f9da8753b5e1038e7f4cb02f73 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 25 Feb 2021 22:21:20 +0000 Subject: [PATCH 02/14] fix bracket --- include/rapidjson/schema.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index a6cbfed1e..082fbbde0 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -1849,7 +1849,7 @@ class GenericSchemaDocument { */ explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0, - const PointerType& pointer = PointerType() : // PR #1393 + const PointerType& pointer = PointerType()) : // PR #1393 remoteProvider_(remoteProvider), allocator_(allocator), ownAllocator_(), From ad73c032e714af439d0aadb7946bf63706824736 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 25 Feb 2021 22:51:35 +0000 Subject: [PATCH 03/14] fix compile errors --- example/schemavalidator/schemavalidator.cpp | 2 ++ include/rapidjson/schema.h | 2 +- test/unittest/schematest.cpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/example/schemavalidator/schemavalidator.cpp b/example/schemavalidator/schemavalidator.cpp index bffd64ae0..8c7e26c79 100644 --- a/example/schemavalidator/schemavalidator.cpp +++ b/example/schemavalidator/schemavalidator.cpp @@ -2,6 +2,8 @@ // The example validates JSON text from stdin with a JSON schema specified in the argument. +#define RAPIDJSON_HAS_STDSTRING 1 + #include "rapidjson/error/en.h" #include "rapidjson/filereadstream.h" #include "rapidjson/schema.h" diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 082fbbde0..e9ed19d08 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -2118,7 +2118,7 @@ class GenericSchemaDocument { SchemaType* typeless_; internal::Stack schemaMap_; // Stores created Pointer -> Schemas internal::Stack schemaRef_; // Stores Pointer(s) from $ref(s) until resolved - SValue uri_; + SValue uri_; // Schema document URI }; //! GenericSchemaDocument using Value type. diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index b3b929195..d84fcd495 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -2136,7 +2136,7 @@ class RemoteSchemaDocumentProvider : public IGenericRemoteSchemaDocumentProvider virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) { for (size_t i = 0; i < kCount; i++) - if (typename SchemaDocumentType::SType(uri, length) == sd_[i]->GetURI()) + if (typename SchemaDocumentType::SValue(uri, length) == sd_[i]->GetURI()) return sd_[i]; return 0; } From fe1a29ca6910f2b5ac246c42e09b4db23796f861 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 25 Feb 2021 23:54:44 +0000 Subject: [PATCH 04/14] fix platform-dependent compiler error with >> --- test/unittest/schematest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index d84fcd495..a0763893f 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -2918,7 +2918,7 @@ TEST(SchemaValidator, Schema_UnknownError) { TEST(SchemaValidator, Uri_Parse) { typedef std::basic_string String; - typedef Uri >> Uri; + typedef Uri > > Uri; MemoryPoolAllocator allocator; String s = "http://auth/path?query#frag"; @@ -3028,7 +3028,7 @@ TEST(SchemaValidator, Uri_Parse) { TEST(SchemaValidator, Uri_Resolve) { typedef std::basic_string String; - typedef Uri >> Uri; + typedef Uri > > Uri; // ref is full uri Uri base = Uri(String("http://auth/path/#frag")); From 6b57738e4a8abb62b17d5ab0ab619ef4a652d026 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 11 Mar 2021 14:49:28 +0000 Subject: [PATCH 05/14] handle internal refs properly --- bin/unittestschema/idandref.json | 69 ++++++ include/rapidjson/pointer.h | 66 +++++ include/rapidjson/schema.h | 409 ++++++++++--------------------- include/rapidjson/uri.h | 259 +++++++++++++++++++ test/unittest/CMakeLists.txt | 1 + test/unittest/pointertest.cpp | 42 ++++ test/unittest/schematest.cpp | 277 +++------------------ test/unittest/uritest.cpp | 278 +++++++++++++++++++++ 8 files changed, 873 insertions(+), 528 deletions(-) create mode 100644 bin/unittestschema/idandref.json create mode 100644 include/rapidjson/uri.h create mode 100644 test/unittest/uritest.cpp diff --git a/bin/unittestschema/idandref.json b/bin/unittestschema/idandref.json new file mode 100644 index 000000000..ad485d29f --- /dev/null +++ b/bin/unittestschema/idandref.json @@ -0,0 +1,69 @@ +{ + "id": "http://example.com/root.json", + "definitions": { + "A": { + "id": "#foo", + "type": "integer" + }, + "B": { + "id": "other.json", + "definitions": { + "X": { + "id": "#bar", + "type": "boolean" + }, + "Y": { + "$ref": "#/definitions/X" + }, + "W": { + "$ref": "#/definitions/Y" + }, + "Z": { + "$ref": "#bar" + }, + "N": { + "properties": { + "NX": { + "$ref": "#/definitions/X" + } + } + } + } + } + }, + "properties": { + "PA1": { + "$ref": "http://example.com/root.json#/definitions/A" + }, + "PA2": { + "$ref": "#/definitions/A" + }, + "PA3": { + "$ref": "#foo" + }, + "PX1": { + "$ref": "#/definitions/B/definitions/X" + }, + "PX2Y": { + "$ref": "#/definitions/B/definitions/Y" + }, + "PX3Z": { + "$ref": "#/definitions/B/definitions/Z" + }, + "PX4": { + "$ref": "http://example.com/other.json#/definitions/X" + }, + "PX5": { + "$ref": "other.json#/definitions/X" + }, + "PX6": { + "$ref": "other.json#bar" + }, + "PX7W": { + "$ref": "#/definitions/B/definitions/W" + }, + "PX8N": { + "$ref": "#/definitions/B/definitions/N" + } + } +} \ No newline at end of file diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 90e5903bc..b5e952b3b 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 @@ class GenericPointer { //@} + //!@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 e9ed19d08..e290fc147 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 @@ class Schema { 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 @@ class Schema { // 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 @@ class Schema { return id_; } - const PointerType& GetPointer() const { + const PointerType& GetPointer() const { return pointer_; } @@ -806,7 +804,7 @@ class Schema { 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 @@ class GenericSchemaDocument { 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 @@ class GenericSchemaDocument { 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 @@ class GenericSchemaDocument { 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 @@ class GenericSchemaDocument { // 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 @@ class GenericSchemaDocument { } // 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 @@ class GenericSchemaDocument { *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; - } - } - } 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; + 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; + } + } } - } } } } @@ -2043,36 +1875,46 @@ class GenericSchemaDocument { 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; - 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++; + } + // 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; - default: - return resval; + 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_)); + } + if (resval) break; + i++; + } } + return resval; } // Added by PR #1393 @@ -2119,6 +1961,7 @@ class GenericSchemaDocument { 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 000000000..04bd92e9e --- /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 fc8803eff..6439c361d 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 43718038f..c693b8fa2 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 a0763893f..1b25e2f44 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 000000000..d8a78b831 --- /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 From 6c9da69abf2f121042032e8510ac843e30eff69f Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 11 Mar 2021 15:06:02 +0000 Subject: [PATCH 06/14] remove comma --- include/rapidjson/schema.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index e290fc147..226fea4bc 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -1692,7 +1692,7 @@ class GenericSchemaDocument { schemaMap_(std::move(rhs.schemaMap_)), schemaRef_(std::move(rhs.schemaRef_)), uri_(std::move(rhs.uri_)), - docId_(rhs.docId_), + docId_(rhs.docId_) { rhs.remoteProvider_ = 0; rhs.allocator_ = 0; From 8768b5b1d696935bea927ee147e0bf81c933cbd0 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 11 Mar 2021 15:13:17 +0000 Subject: [PATCH 07/14] correct #defines in uri.h --- include/rapidjson/uri.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/rapidjson/uri.h b/include/rapidjson/uri.h index 04bd92e9e..1e5b46fdc 100644 --- a/include/rapidjson/uri.h +++ b/include/rapidjson/uri.h @@ -15,13 +15,11 @@ #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) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated #endif RAPIDJSON_NAMESPACE_BEGIN From 32722fa31ddaf5abffa1cb070530374f689c7669 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 11 Mar 2021 16:53:05 +0000 Subject: [PATCH 08/14] satisfy all compilers --- include/rapidjson/schema.h | 10 +++++----- include/rapidjson/uri.h | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 226fea4bc..5f25b9b98 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -1831,7 +1831,7 @@ class GenericSchemaDocument { const PointerType relPointer(s, len, allocator_); if (relPointer.IsValid()) { // Get the subschema - if (const ValueType *v = relPointer.Get(*base)) { + if (const ValueType *pv = 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++) @@ -1842,7 +1842,7 @@ class GenericSchemaDocument { // 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); + CreateSchema(schema, pointer, *pv, document, scopeId); return true; } } @@ -1852,14 +1852,14 @@ class GenericSchemaDocument { // 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)) { + if (const ValueType *pv = FindId(*base, ref, pointer, UriType(ref.GetBase()), true, basePointer)) { + if (!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); + CreateSchema(schema, pointer, *pv, document, scopeId); return true; } } diff --git a/include/rapidjson/uri.h b/include/rapidjson/uri.h index 1e5b46fdc..e72976d55 100644 --- a/include/rapidjson/uri.h +++ b/include/rapidjson/uri.h @@ -34,18 +34,18 @@ class GenericUri { typedef std::basic_string String; // Constructors - GenericUri() {} + GenericUri() : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_() {} - GenericUri(const String& uri) { + GenericUri(const String& uri) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_() { Parse(uri); } - GenericUri(const Ch* uri, SizeType len) { + GenericUri(const Ch* uri, SizeType len) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_() { Parse(String(uri, len)); } // Use with specializations of GenericValue - template GenericUri(const T& uri) { + template GenericUri(const T& uri) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_() { Parse(uri.template Get()); } @@ -236,13 +236,13 @@ class GenericUri { //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 # + String uri_; // Full uri + String base_; // Everything except fragment + String scheme_; // Includes the : + String auth_; // Includes the // + String path_; // Absolute if starts with / + String query_; // Includes the ? + String frag_; // Includes the # }; //! GenericUri for Value (UTF-8, default allocator). From 24b9b7e276995fd89bff424966bf2a9eea7624dd Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 11 Mar 2021 18:16:24 +0000 Subject: [PATCH 09/14] satisfy all compilers 2 --- include/rapidjson/schema.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 5f25b9b98..933e0eb92 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -1898,7 +1898,7 @@ class GenericSchemaDocument { return resval; } // No match, continue looking - for (typename ValueType::ConstMemberIterator m = doc.MemberBegin(); m != doc.MemberEnd(); ++m) { + for (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_)); } From bc026e3fb574411acc4f2be82177abb8b5b92202 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 11 Mar 2021 18:25:10 +0000 Subject: [PATCH 10/14] satisfy all compilers 3 --- test/unittest/uritest.cpp | 146 +++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/test/unittest/uritest.cpp b/test/unittest/uritest.cpp index d8a78b831..b5eda2957 100644 --- a/test/unittest/uritest.cpp +++ b/test/unittest/uritest.cpp @@ -31,13 +31,13 @@ using namespace rapidjson; TEST(Uri, Parse) { typedef std::basic_string String; - typedef GenericUri > Uri; + typedef GenericUri > UriType; MemoryPoolAllocator allocator; String s = "http://auth/path?query#frag"; Value v; v.SetString(s, allocator); - Uri u = Uri(v); + UriType u = UriType(v); EXPECT_TRUE(u.GetScheme() == "http:"); EXPECT_TRUE(u.GetAuth() == "//auth"); EXPECT_TRUE(u.GetPath() == "/path"); @@ -50,7 +50,7 @@ TEST(Uri, Parse) { s = "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"; v.SetString(s, allocator); - u = Uri(v); + u = UriType(v); EXPECT_TRUE(u.GetScheme() == "urn:"); EXPECT_TRUE(u.GetAuth() == ""); EXPECT_TRUE(u.GetPath() == "uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"); @@ -62,7 +62,7 @@ TEST(Uri, Parse) { s = ""; v.SetString(s, allocator); - u = Uri(v); + u = UriType(v); EXPECT_TRUE(u.GetScheme() == ""); EXPECT_TRUE(u.GetAuth() == ""); EXPECT_TRUE(u.GetPath() == ""); @@ -72,7 +72,7 @@ TEST(Uri, Parse) { s = "http://auth/"; v.SetString(s, allocator); - u = Uri(v); + u = UriType(v); EXPECT_TRUE(u.GetScheme() == "http:"); EXPECT_TRUE(u.GetAuth() == "//auth"); EXPECT_TRUE(u.GetPath() == "/"); @@ -81,7 +81,7 @@ TEST(Uri, Parse) { EXPECT_TRUE(u.GetFrag() == ""); s = "/path/sub"; - u = Uri(s); + u = UriType(s); EXPECT_TRUE(u.GetScheme() == ""); EXPECT_TRUE(u.GetAuth() == ""); EXPECT_TRUE(u.GetPath() == "/path/sub"); @@ -91,7 +91,7 @@ TEST(Uri, Parse) { // absolute path gets normalized s = "/path/../sub/"; - u = Uri(s); + u = UriType(s); EXPECT_TRUE(u.GetScheme() == ""); EXPECT_TRUE(u.GetAuth() == ""); EXPECT_TRUE(u.GetPath() == "/sub/"); @@ -101,7 +101,7 @@ TEST(Uri, Parse) { // relative path does not s = "path/../sub"; - u = Uri(s); + u = UriType(s); EXPECT_TRUE(u.GetScheme() == ""); EXPECT_TRUE(u.GetAuth() == ""); EXPECT_TRUE(u.GetPath() == "path/../sub"); @@ -110,7 +110,7 @@ TEST(Uri, Parse) { EXPECT_TRUE(u.GetFrag() == ""); s = "http://auth#frag/stuff"; - u = Uri(s); + u = UriType(s); EXPECT_TRUE(u.GetScheme() == "http:"); EXPECT_TRUE(u.GetAuth() == "//auth"); EXPECT_TRUE(u.GetPath() == ""); @@ -120,7 +120,7 @@ TEST(Uri, Parse) { EXPECT_TRUE(u.Get() == s); s = "#frag/stuff"; - u = Uri(s); + u = UriType(s); EXPECT_TRUE(u.GetScheme() == ""); EXPECT_TRUE(u.GetAuth() == ""); EXPECT_TRUE(u.GetPath() == ""); @@ -130,7 +130,7 @@ TEST(Uri, Parse) { EXPECT_TRUE(u.Get() == s); Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'}; - u = Uri(c, 11); + u = UriType(c, 11); EXPECT_TRUE(String(u.GetString()) == "#frag/stuff"); EXPECT_TRUE(u.GetStringLength() == 11); EXPECT_TRUE(String(u.GetBaseString()) == ""); @@ -141,135 +141,135 @@ TEST(Uri, Parse) { TEST(Uri, Resolve) { typedef std::basic_string String; - typedef GenericUri > Uri; + typedef GenericUri > UriType; // ref is full uri - Uri base = Uri(String("http://auth/path/#frag")); - Uri ref = Uri(String("http://newauth/newpath#newfrag")); + UriType base = UriType(String("http://auth/path/#frag")); + UriType ref = UriType(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")); + base = UriType(String("/path/#frag")); + ref = UriType(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")); + base = UriType(String("http://auth/path/#frag")); + ref = UriType(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")); + base = UriType(String("http://auth/path/#")); + ref = UriType(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#")); + base = UriType(String("http://auth/path/file.json#frag")); + ref = UriType(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")); + base = UriType(String("http://auth/path/file.json#frag/stuff")); + ref = UriType(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")); + base = UriType(String("file.json")); + ref = UriType(String("newfile.json")); EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - base = Uri(String("file.json")); - ref = Uri(String("./newfile.json")); + base = UriType(String("file.json")); + ref = UriType(String("./newfile.json")); EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - base = Uri(String("file.json")); - ref = Uri(String("parent/../newfile.json")); + base = UriType(String("file.json")); + ref = UriType(String("parent/../newfile.json")); EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - base = Uri(String("file.json")); - ref = Uri(String("parent/./newfile.json")); + base = UriType(String("file.json")); + ref = UriType(String("parent/./newfile.json")); EXPECT_TRUE(ref.Resolve(base).Get() == "parent/newfile.json"); - base = Uri(String("file.json")); - ref = Uri(String("../../parent/.././newfile.json")); + base = UriType(String("file.json")); + ref = UriType(String("../../parent/.././newfile.json")); EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - base = Uri(String("http://auth")); - ref = Uri(String("newfile.json")); + base = UriType(String("http://auth")); + ref = UriType(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")); + base = UriType(String("#frag/stuff")); + ref = UriType(String("#newfrag/newstuff")); EXPECT_TRUE(ref.Resolve(base).Get() == "#newfrag/newstuff"); // test ref fragment always wins - base = Uri(String("/path#frag")); - ref = Uri(String("")); + base = UriType(String("/path#frag")); + ref = UriType(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")); + base = UriType(String("http://a/b/c/d;p?q")); + ref = UriType(String("g:h")); EXPECT_TRUE(ref.Resolve(base).Get() == "g:h"); - ref = Uri(String("g")); + ref = UriType(String("g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g"); - ref = Uri(String("./g")); + ref = UriType(String("./g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g"); - ref = Uri(String("g/")); + ref = UriType(String("g/")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g/"); - ref = Uri(String("/g")); + ref = UriType(String("/g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("//g")); + ref = UriType(String("//g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://g"); - ref = Uri(String("?y")); + ref = UriType(String("?y")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?y"); - ref = Uri(String("g?y")); + ref = UriType(String("g?y")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y"); - ref = Uri(String("#s")); + ref = UriType(String("#s")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q#s"); - ref = Uri(String("g#s")); + ref = UriType(String("g#s")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s"); - ref = Uri(String("g?y#s")); + ref = UriType(String("g?y#s")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y#s"); - ref = Uri(String(";x")); + ref = UriType(String(";x")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/;x"); - ref = Uri(String("g;x")); + ref = UriType(String("g;x")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x"); - ref = Uri(String("g;x?y#s")); + ref = UriType(String("g;x?y#s")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x?y#s"); - ref = Uri(String("")); + ref = UriType(String("")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q"); - ref = Uri(String(".")); + ref = UriType(String(".")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/"); - ref = Uri(String("./")); + ref = UriType(String("./")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/"); - ref = Uri(String("..")); + ref = UriType(String("..")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/"); - ref = Uri(String("../")); + ref = UriType(String("../")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/"); - ref = Uri(String("../g")); + ref = UriType(String("../g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/g"); - ref = Uri(String("../..")); + ref = UriType(String("../..")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/"); - ref = Uri(String("../../")); + ref = UriType(String("../../")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/"); - ref = Uri(String("../../g")); + ref = UriType(String("../../g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("../../../g")); + ref = UriType(String("../../../g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("../../../../g")); + ref = UriType(String("../../../../g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("/./g")); + ref = UriType(String("/./g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("/../g")); + ref = UriType(String("/../g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = Uri(String("g.")); + ref = UriType(String("g.")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g."); - ref = Uri(String(".g")); + ref = UriType(String(".g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/.g"); - ref = Uri(String("g..")); + ref = UriType(String("g..")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g.."); - ref = Uri(String("..g")); + ref = UriType(String("..g")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/..g"); - ref = Uri(String("g#s/../x")); + ref = UriType(String("g#s/../x")); EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s/../x"); } From 6e58a53f44164a51266772e39bfa6e6167975ac2 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 20 May 2021 17:44:34 +0100 Subject: [PATCH 11/14] fix coverage --- test/unittest/pointertest.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index c693b8fa2..39c7ec61b 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -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. +// 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 @@ -676,14 +677,15 @@ TEST(Pointer, GetUri) { 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 + EXPECT_TRUE((Pointer("/abc").GetUri(d, Pointer::UriType(doc)).Get()) == Pointer::UriType().Get()); // Out of boundary size_t unresolvedTokenIndex; - EXPECT_TRUE((Pointer("/foo/3").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == ""); // Out of boundary + EXPECT_TRUE((Pointer("/foo/3").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == Pointer::UriType().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_TRUE((Pointer("/foo/a").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == Pointer::UriType().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_TRUE((Pointer("/foo/0/0").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == Pointer::UriType().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_TRUE((Pointer("/foo/0/a").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == Pointer::UriType().Get()); // "/foo/0" is an string, cannot further query EXPECT_EQ(2u, unresolvedTokenIndex); Pointer::Token tokens[] = { { "foo ...", 3, kPointerInvalidIndex } }; @@ -706,7 +708,8 @@ TEST(Pointer, Get) { EXPECT_EQ(&d["k\"l"], Pointer("/k\"l").Get(d)); EXPECT_EQ(&d[" "], Pointer("/ ").Get(d)); EXPECT_EQ(&d["m~n"], Pointer("/m~0n").Get(d)); - EXPECT_TRUE(Pointer("/abc").Get(d) == 0); + + EXPECT_TRUE(Pointer("/abc").Get(d) == 0); // Out of boundary size_t unresolvedTokenIndex; EXPECT_TRUE(Pointer("/foo/2").Get(d, &unresolvedTokenIndex) == 0); // Out of boundary EXPECT_EQ(1u, unresolvedTokenIndex); From 494447b731b12160ac60a76a738d106863680129 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Fri, 21 May 2021 15:55:11 +0100 Subject: [PATCH 12/14] remove copyright & debug statements --- include/rapidjson/schema.h | 1 - include/rapidjson/uri.h | 4 ---- test/unittest/pointertest.cpp | 1 - 3 files changed, 6 deletions(-) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 933e0eb92..791aca541 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -1,7 +1,6 @@ // 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 diff --git a/include/rapidjson/uri.h b/include/rapidjson/uri.h index e72976d55..98b0a158c 100644 --- a/include/rapidjson/uri.h +++ b/include/rapidjson/uri.h @@ -110,7 +110,6 @@ class GenericUri { } base_ = scheme_ + auth_ + path_ + query_; uri_ = base_ + frag_; - //std::cout << " Resolved uri: " << uri_ << std::endl; return *this; } @@ -196,7 +195,6 @@ class GenericUri { } 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 @@ -209,7 +207,6 @@ class GenericUri { 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); @@ -233,7 +230,6 @@ class GenericUri { // 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_; // Full uri diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 39c7ec61b..a835af42d 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -1,7 +1,6 @@ // 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. -// 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 From 28bcbd3f3578aa3890795dec5a97570d59d9256a Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Tue, 8 Jun 2021 10:53:10 +0100 Subject: [PATCH 13/14] make std::string optional --- include/rapidjson/internal/strfunc.h | 14 + include/rapidjson/pointer.h | 5 +- include/rapidjson/schema.h | 15 +- include/rapidjson/uri.h | 522 ++++++++++++----- test/unittest/pointertest.cpp | 43 +- test/unittest/uritest.cpp | 832 +++++++++++++++++++-------- 6 files changed, 1015 insertions(+), 416 deletions(-) diff --git a/include/rapidjson/internal/strfunc.h b/include/rapidjson/internal/strfunc.h index baecb6cc8..b698a8f43 100644 --- a/include/rapidjson/internal/strfunc.h +++ b/include/rapidjson/internal/strfunc.h @@ -45,6 +45,20 @@ inline SizeType StrLen(const wchar_t* s) { return SizeType(std::wcslen(s)); } +//! Custom strcmpn() which works on different character types. +/*! \tparam Ch Character type (e.g. char, wchar_t, short) + \param s1 Null-terminated input string. + \param s2 Null-terminated input string. + \return 0 if equal +*/ +template +inline int StrCmp(const Ch* s1, const Ch* s2) { + RAPIDJSON_ASSERT(s1 != 0); + RAPIDJSON_ASSERT(s2 != 0); + while(*s1 && (*s1 == *s2)) { s1++; s2++; } + return static_cast(*s1) < static_cast(*s2) ? -1 : static_cast(*s1) > static_cast(*s2); +} + //! Returns number of code points in a encoded string. template bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) { diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index b5e952b3b..96f33fa8a 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -554,8 +554,7 @@ class GenericPointer { // 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); + UriType here = UriType(m->value, allocator_).Resolve(base, allocator_); base = here; } m = v->FindMember(GenericValue(GenericStringRef(t->name, t->length))); @@ -576,7 +575,7 @@ class GenericPointer { // Error: unresolved token if (unresolvedTokenIndex) *unresolvedTokenIndex = static_cast(t - tokens_); - return UriType(); + return UriType(allocator_); } return base; } diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 791aca541..a48288b9f 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -494,9 +494,8 @@ class Schema { // If we have an id property, resolve it with the in-scope id if (const ValueType* v = GetMember(value, GetIdString())) { if (v->IsString()) { - UriType local = UriType(*v); - local.Resolve(id_); - id_ = local; + UriType local(*v, allocator); + id_ = local.Resolve(id_, allocator); } } @@ -1659,7 +1658,7 @@ class GenericSchemaDocument { Ch noUri[1] = {0}; uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); - docId_ = UriType(uri_); + docId_ = UriType(uri_, allocator_); typeless_ = static_cast(allocator_->Malloc(sizeof(SchemaType))); new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, docId_); @@ -1792,8 +1791,7 @@ class GenericSchemaDocument { if (len > 0) { // First resolve $ref against the in-scope id UriType scopeId = id; - UriType ref = UriType(itr->value); - ref.Resolve(scopeId); + UriType ref = UriType(itr->value, allocator_).Resolve(scopeId, allocator_); // 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(); @@ -1851,7 +1849,7 @@ class GenericSchemaDocument { // 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 *pv = FindId(*base, ref, pointer, UriType(ref.GetBase()), true, basePointer)) { + if (const ValueType *pv = FindId(*base, ref, pointer, UriType(ref.GetBaseString(), ref.GetBaseStringLength(), allocator_), true, basePointer)) { if (!IsCyclicRef(pointer)) { //GenericStringBuffer sb; //pointer.StringifyUriFragment(sb); @@ -1887,8 +1885,7 @@ class GenericSchemaDocument { // 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); + localuri = UriType(m->value, allocator_).Resolve(baseuri, allocator_); } // See if it matches if (localuri.Match(finduri, full)) { diff --git a/include/rapidjson/uri.h b/include/rapidjson/uri.h index 98b0a158c..640d793ed 100644 --- a/include/rapidjson/uri.h +++ b/include/rapidjson/uri.h @@ -1,20 +1,22 @@ // 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. +// +// (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 // // 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 +// 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_ +#include "internal/strfunc.h" + #if defined(__clang__) RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_OFF(c++98-compat) @@ -27,222 +29,462 @@ RAPIDJSON_NAMESPACE_BEGIN /////////////////////////////////////////////////////////////////////////////// // GenericUri -template -class GenericUri { -public: + template + class GenericUri { + public: typedef typename ValueType::Ch Ch; +#if RAPIDJSON_HAS_STDSTRING typedef std::basic_string String; +#endif - // Constructors - GenericUri() : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_() {} + //! Constructors + GenericUri(Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() { + //std::cout << "Default constructor" << std::endl; + } - GenericUri(const String& uri) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_() { - Parse(uri); + GenericUri(const Ch* uri, SizeType len, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() { + Parse(uri, len); } - GenericUri(const Ch* uri, SizeType len) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_() { - Parse(String(uri, len)); + GenericUri(const Ch* uri, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() { + Parse(uri, internal::StrLen(uri)); } // Use with specializations of GenericValue - template GenericUri(const T& uri) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_() { - Parse(uri.template Get()); + template GenericUri(const T& uri, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() { + const Ch* u = uri.template Get(); // TypeHelper from document.h + Parse(u, internal::StrLen(u)); } - // Getters - const String& Get() const { return uri_; } +#if RAPIDJSON_HAS_STDSTRING + GenericUri(const String& uri, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() { + Parse(uri.c_str(), internal::StrLen(uri.c_str())); + } +#endif + + //! Copy constructor + GenericUri(const GenericUri& rhs) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(rhs.allocator_), ownAllocator_() { + //std::cout << "Copy constructor" << std::endl; + *this = rhs; + } + //! Copy constructor + GenericUri(const GenericUri& rhs, Allocator* allocator) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() { + //std::cout << "Copy constructor" << std::endl; + *this = rhs; + } + + //! Destructor. + ~GenericUri() { + //std::cout << "Destructor" << std::endl; + Free(); + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Assignment operator + GenericUri& operator=(const GenericUri& rhs) { + //std::cout << "Operator=" << std::endl; + if (this != &rhs) { + // Do not delete ownAllocator + Free(); + Allocate(rhs.GetStringLength()); + auth_ = CopyPart(scheme_, rhs.scheme_, rhs.GetSchemeStringLength()); + path_ = CopyPart(auth_, rhs.auth_, rhs.GetAuthStringLength()); + query_ = CopyPart(path_, rhs.path_, rhs.GetPathStringLength()); + frag_ = CopyPart(query_, rhs.query_, rhs.GetQueryStringLength()); + base_ = CopyPart(frag_, rhs.frag_, rhs.GetFragStringLength()); + uri_ = CopyPart(base_, rhs.base_, rhs.GetBaseStringLength()); + CopyPart(uri_, rhs.uri_, rhs.GetStringLength()); + //std::wcout << L" Assignment uri: " << uri_ << ", length: " << GetStringLength() << std::endl; + } + return *this; + } + + //! Getters // Use with specializations of GenericValue template void Get(T& uri, Allocator& allocator) { - uri.template Set(this->Get(), allocator); + uri.template Set(this->GetString(), allocator); // TypeHelper from document.h } - 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_; } + SizeType GetStringLength() const { return internal::StrLen(uri_); } + const Ch* GetBaseString() const { return base_; } + SizeType GetBaseStringLength() const { return internal::StrLen(base_); } + const Ch* GetSchemeString() const { return scheme_; } + SizeType GetSchemeStringLength() const { return internal::StrLen(scheme_); } + const Ch* GetAuthString() const { return auth_; } + SizeType GetAuthStringLength() const { return internal::StrLen(auth_); } + const Ch* GetPathString() const { return path_; } + SizeType GetPathStringLength() const { return internal::StrLen(path_); } + const Ch* GetQueryString() const { return query_; } + SizeType GetQueryStringLength() const { return internal::StrLen(query_); } + const Ch* GetFragString() const { return frag_; } + SizeType GetFragStringLength() const { return internal::StrLen(frag_); } - const Ch* GetString() const { return uri_.c_str(); } - SizeType GetStringLength() const { return static_cast(uri_.length()); } +#if RAPIDJSON_HAS_STDSTRING + static String Get(const GenericUri& uri) { return String(uri.GetString(), uri.GetStringLength()); } + static String GetBase(const GenericUri& uri) { return String(uri.GetBaseString(), uri.GetBaseStringLength()); } + static String GetScheme(const GenericUri& uri) { return String(uri.GetSchemeString(), uri.GetSchemeStringLength()); } + static String GetAuth(const GenericUri& uri) { return String(uri.GetAuthString(), uri.GetAuthStringLength()); } + static String GetPath(const GenericUri& uri) { return String(uri.GetPathString(), uri.GetPathStringLength()); } + static String GetQuery(const GenericUri& uri) { return String(uri.GetQueryString(), uri.GetQueryStringLength()); } + static String GetFrag(const GenericUri& uri) { return String(uri.GetFragString(), uri.GetFragStringLength()); } +#endif - const Ch* GetBaseString() const { return base_.c_str(); } - SizeType GetBaseStringLength() const { return static_cast(base_.length()); } + //! Equality operators + bool operator==(const GenericUri& rhs) const { + return Match(rhs, true); + } - const Ch* GetFragString() const { return frag_.c_str(); } - SizeType GetFragStringLength() const { return static_cast(frag_.length()); } + bool operator!=(const GenericUri& rhs) const { + return !Match(rhs, true); + } - // Resolve this URI against another URI in accordance with URI resolution rules at - // https://tools.ietf.org/html/rfc3986 + bool Match(const GenericUri& uri, bool full) const { + Ch* s1; + Ch* s2; + if (full) { + s1 = uri_; + s2 = uri.uri_; + } else { + s1 = base_; + s2 = uri.base_; + } + if (s1 == s2) return true; + if (s1 == 0 || s2 == 0) return false; + return internal::StrCmp(s1, s2) == 0; + } + + //! Resolve this URI against another (base) URI in accordance with URI resolution rules. + // See 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()) { + // Returns a new GenericUri for the resolved URI. + GenericUri Resolve(const GenericUri& baseuri, Allocator* allocator = 0) { + //std::cout << "Resolve" << std::endl; + GenericUri resuri; + resuri.allocator_ = allocator; + // Ensure enough space for combining paths + resuri.Allocate(GetStringLength() + baseuri.GetStringLength() + 1); // + 1 for joining slash + + if (!(GetSchemeStringLength() == 0)) { // Use all of this URI - RemoveDotSegments(path_); + resuri.auth_ = CopyPart(resuri.scheme_, scheme_, GetSchemeStringLength()); + resuri.path_ = CopyPart(resuri.auth_, auth_, GetAuthStringLength()); + resuri.query_ = CopyPart(resuri.path_, path_, GetPathStringLength()); + resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength()); + resuri.RemoveDotSegments(); } else { - if (!auth_.empty()) { - RemoveDotSegments(path_); + // Use the base scheme + resuri.auth_ = CopyPart(resuri.scheme_, baseuri.scheme_, baseuri.GetSchemeStringLength()); + if (!(GetAuthStringLength() == 0)) { + // Use this auth, path, query + resuri.path_ = CopyPart(resuri.auth_, auth_, GetAuthStringLength()); + resuri.query_ = CopyPart(resuri.path_, path_, GetPathStringLength()); + resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength()); + resuri.RemoveDotSegments(); } else { - if (path_.empty()) { - path_ = uri.GetPath(); - if (query_.empty()) { - query_ = uri.GetQuery(); + // Use the base auth + resuri.path_ = CopyPart(resuri.auth_, baseuri.auth_, baseuri.GetAuthStringLength()); + if (GetPathStringLength() == 0) { + // Use the base path + resuri.query_ = CopyPart(resuri.path_, baseuri.path_, baseuri.GetPathStringLength()); + if (GetQueryStringLength() == 0) { + // Use the base query + resuri.frag_ = CopyPart(resuri.query_, baseuri.query_, baseuri.GetQueryStringLength()); + } else { + // Use this query + resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength()); } } else { - static const String slash = GetSlashString().GetString(); - if (path_.find(slash) == 0) { - // Absolute path - replace all the path - RemoveDotSegments(path_); + if (path_[0] == '/') { + // Absolute path - use all of this path + resuri.query_ = CopyPart(resuri.path_, path_, GetPathStringLength()); + resuri.RemoveDotSegments(); } 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_); + // Relative path - append this path to base path after base path's last slash + size_t pos = 0; + if (!(baseuri.GetAuthStringLength() == 0) && baseuri.GetPathStringLength() == 0) { + resuri.path_[pos] = '/'; + pos++; + } + size_t lastslashpos = baseuri.GetPathStringLength(); + while (lastslashpos > 0) { + if (baseuri.path_[lastslashpos - 1] == '/') break; + lastslashpos--; + } + std::memcpy(&resuri.path_[pos], baseuri.path_, lastslashpos * sizeof(Ch)); + pos += lastslashpos; + resuri.query_ = CopyPart(&resuri.path_[pos], path_, GetPathStringLength()); + resuri.RemoveDotSegments(); } + // Use this query + resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength()); } - auth_ = uri.GetAuth(); } - scheme_ = uri.GetScheme(); } - base_ = scheme_ + auth_ + path_ + query_; - uri_ = base_ + frag_; - return *this; - } + // Always use this frag + resuri.base_ = CopyPart(resuri.frag_, frag_, GetFragStringLength()); + //std::wcout << L" Resolved uri: " << L"s: " << resuri.scheme_ << L" a: " << resuri.auth_ << L" p: " << resuri.path_ << L" q: " << resuri.query_ << L" f: " << resuri.frag_ << std::endl; - bool Match(const GenericUri& uri, bool full) const { - if (full) - return uri_ == uri.Get(); - else - return base_ == uri.GetBase(); + // Re-constitute base_ and uri_ + resuri.SetBase(); + resuri.uri_ = resuri.base_ + resuri.GetBaseStringLength() + 1; + resuri.SetUri(); + //std::wcout << L" Resolved rebuilt uri: " << resuri.uri_ << L", length: " << resuri.GetStringLength() << std::endl; + return resuri; } - // 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;\ - } + //! Get the allocator of this GenericUri. + Allocator& GetAllocator() { return *allocator_; } + +private: + // Allocate memory for a URI + // Returns total amount allocated + std::size_t Allocate(std::size_t len) { + //std::cout << "Allocate" << std::endl; + // Create own allocator if user did not supply. + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); - RAPIDJSON_STRING_(SchemeEnd, ':') - RAPIDJSON_STRING_(AuthStart, '/', '/') - RAPIDJSON_STRING_(QueryStart, '?') - RAPIDJSON_STRING_(FragStart, '#') - RAPIDJSON_STRING_(Slash, '/') - RAPIDJSON_STRING_(Dot, '.') + // Allocate one block containing each part of the URI (5) plus base plus full URI, all null terminated. + // Order: scheme, auth, path, query, frag, base, uri + size_t total = (3 * len + 7) * sizeof(Ch); + scheme_ = static_cast(allocator_->Malloc(total)); + *scheme_ = '\0'; + auth_ = scheme_ + 1; + *auth_ = '\0'; + path_ = auth_ + 1; + *path_ = '\0'; + query_ = path_ + 1; + *query_ = '\0'; + frag_ = query_ + 1; + *frag_ = '\0'; + base_ = frag_ + 1; + *base_ = '\0'; + uri_ = base_ + 1; + *uri_ = '\0'; + //std::cout << " Allocating " << total << std::endl; + return total; + } -#undef RAPIDJSON_STRING_ + // Free memory for a URI + void Free() { + //std::cout << "Free" << std::endl; + if (scheme_) { + //std::cout << " Freeing" << std::endl; + Allocator::Free(scheme_); + scheme_ = 0; + } + } -private: - // Parse a URI into constituent scheme, authority, path, query, fragment + // Parse a URI into constituent scheme, authority, path, query, & fragment parts // Supports URIs that match regex ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? as per // https://tools.ietf.org/html/rfc3986 - void Parse(const String& uri) { + void Parse(const Ch* uri, std::size_t len) { + //std::cout << "Parse" << std::endl; 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(); + Allocate(len); + // Look for scheme ([^:/?#]+):)? if (start < len) { - pos1 = uri.find(schemeEnd); - if (pos1 != std::string::npos) { - pos2 = uri.find_first_of(pathStart + queryStart + fragStart); + while (pos1 < len) { + if (uri[pos1] == ':') break; + pos1++; + } + if (pos1 != len) { + while (pos2 < len) { + if (uri[pos2] == '/') break; + if (uri[pos2] == '?') break; + if (uri[pos2] == '#') break; + pos2++; + } if (pos1 < pos2) { - pos1 += schemeEnd.length(); - scheme_ = uri.substr(start, pos1); + pos1++; + std::memcpy(scheme_, &uri[start], pos1 * sizeof(Ch)); + scheme_[pos1] = '\0'; start = pos1; } } } // Look for auth (//([^/?#]*))? + auth_ = scheme_ + GetSchemeStringLength() + 1; + *auth_ = '\0'; if (start < len) { - pos1 = uri.find(authStart, start); + pos1 = start; + while (pos1 < len) { + if (uri[pos1] == '/' && uri[pos1 + 1] == '/') break; + pos1++; + } if (pos1 == start) { - pos2 = uri.find_first_of(pathStart + queryStart + fragStart, start + authStart.length()); - auth_ = uri.substr(start, pos2 - start); + pos2 = start + 2; + while (pos2 < len) { + if (uri[pos2] == '/') break; + if (uri[pos2] == '?') break; + if (uri[pos2] == '#') break; + pos2++; + } + std::memcpy(auth_, &uri[start], (pos2 - start) * sizeof(Ch)); + auth_[pos2 - start] = '\0'; start = pos2; } } // Look for path ([^?#]*) + path_ = auth_ + GetAuthStringLength() + 1; + *path_ = '\0'; if (start < len) { - pos2 = uri.find_first_of(queryStart + fragStart, start); + pos2 = start; + while (pos2 < len) { + if (uri[pos2] == '?') break; + if (uri[pos2] == '#') break; + pos2++; + } if (start != pos2) { - path_ = uri.substr(start, pos2 - start); - if (path_.find(pathStart) == 0) { // absolute path - normalize - RemoveDotSegments(path_); - } + std::memcpy(path_, &uri[start], (pos2 - start) * sizeof(Ch)); + path_[pos2 - start] = '\0'; + if (path_[0] == '/') + RemoveDotSegments(); // absolute path - normalize start = pos2; } } // Look for query (\?([^#]*))? + query_ = path_ + GetPathStringLength() + 1; + *query_ = '\0'; if (start < len) { - pos2 = uri.find(fragStart, start); + pos2 = start; + while (pos2 < len) { + if (uri[pos2] == '#') break; + pos2++; + } if (start != pos2) { - query_ = uri.substr(start, pos2 - start); + std::memcpy(query_, &uri[start], (pos2 - start) * sizeof(Ch)); + query_[pos2 - start] = '\0'; start = pos2; } } // Look for fragment (#(.*))? + frag_ = query_ + GetQueryStringLength() + 1; + *frag_ = '\0'; if (start < len) { - frag_ = uri.substr(start); + std::memcpy(frag_, &uri[start], (len - start) * sizeof(Ch)); + frag_[len - start] = '\0'; } - base_ = scheme_ + auth_ + path_ + query_; - uri_ = base_ + frag_; + //std::wcout << L" Parsed uri: " << L"s: " << scheme_ << L" a: " << auth_ << L" p: " << path_ << L" q: " << query_ << L" f: " << frag_ << std::endl; + + // Re-constitute base_ and uri_ + base_ = frag_ + GetFragStringLength() + 1; + SetBase(); + uri_ = base_ + GetBaseStringLength() + 1; + SetUri(); + //std::wcout << L" Rebuilt uri: " << uri_ << L", length: " << GetStringLength() << std::endl; + } + + // Reconstitute base + void SetBase() { + Ch* next = base_; + std::memcpy(next, scheme_, GetSchemeStringLength() * sizeof(Ch)); + next+= GetSchemeStringLength(); + std::memcpy(next, auth_, GetAuthStringLength() * sizeof(Ch)); + next+= GetAuthStringLength(); + std::memcpy(next, path_, GetPathStringLength() * sizeof(Ch)); + next+= GetPathStringLength(); + std::memcpy(next, query_, GetQueryStringLength() * sizeof(Ch)); + next+= GetQueryStringLength(); + *next = '\0'; + } + + // Reconstitute uri + void SetUri() { + Ch* next = uri_; + std::memcpy(next, base_, GetBaseStringLength() * sizeof(Ch)); + next+= GetBaseStringLength(); + std::memcpy(next, frag_, GetFragStringLength() * sizeof(Ch)); + next+= GetFragStringLength(); + *next = '\0'; + } + + // Copy a part from one GenericUri to another + // Return the pointer to the next part to be copied to + Ch* CopyPart(Ch* to, Ch* from, std::size_t len) { + RAPIDJSON_ASSERT(to != 0); + RAPIDJSON_ASSERT(from != 0); + std::memcpy(to, from, len * sizeof(Ch)); + to[len] = '\0'; + Ch* next = to + len + 1; + return next; } - // Remove . and .. segments from a path + // Remove . and .. segments from the path_ member. // 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) { - 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 + // This is done in place as we are only removing segments. + void RemoveDotSegments() { + std::size_t pathlen = GetPathStringLength(); + std::size_t pathpos = 0; // Position in path_ + std::size_t newpos = 0; // Position in new path_ + + // Loop through each segment in original path_ + while (pathpos < pathlen) { + // Get next segment, bounded by '/' or end + size_t slashpos = 0; + while ((pathpos + slashpos) < pathlen) { + if (path_[pathpos + slashpos] == '/') break; + slashpos++; + } + // Check for .. and . segments + if (slashpos == 2 && path_[pathpos] == '.' && path_[pathpos + 1] == '.') { + // Backup a .. segment in the new path_ // 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) { + //std::wcout << L" Path - backing up .. in " << path_ << std::endl; + size_t lastslashpos = newpos; + while (lastslashpos > 0) { + if (path_[lastslashpos - 1] == '/') break; + lastslashpos--; + } + // Make sure we don't go beyond the start segment + if (lastslashpos > 1) { // 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); + lastslashpos--; + while (lastslashpos > 0) { + if (path_[lastslashpos - 1] == '/') break; + lastslashpos--; + } + // Set the new path_ position + newpos = lastslashpos; } + } else if (slashpos == 1 && path_[pathpos] == '.') { + // Discard . segment, leaves new path_ unchanged + //std::wcout << L" Path - removing . in " << path_ << std::endl; } else { - // Copy segment and add slash if not at end - path += seg; - if (pos != std::string::npos) path += slash; + // Move any other kind of segment to the new path_ + RAPIDJSON_ASSERT(newpos <= pathpos); + std::memmove(&path_[newpos], &path_[pathpos], slashpos * sizeof(Ch)); + newpos += slashpos; + // Add slash if not at end + if ((pathpos + slashpos) < pathlen) { + path_[newpos] = '/'; + newpos++; + } } - // Move to next segment if not at end - if (pos != std::string::npos) temp = temp.substr(pos + 1); + // Move to next segment + pathpos += slashpos + 1; } + path_[newpos] = '\0'; + //std::wcout << L" Normalized Path: " << path_ << ", length: " << GetPathStringLength() << std::endl; } - String uri_; // Full uri - String base_; // Everything except fragment - String scheme_; // Includes the : - String auth_; // Includes the // - String path_; // Absolute if starts with / - String query_; // Includes the ? - String frag_; // Includes the # -}; + Ch* uri_; // Everything + Ch* base_; // Everything except fragment + Ch* scheme_; // Includes the : + Ch* auth_; // Includes the // + Ch* path_; // Absolute if starts with / + Ch* query_; // Includes the ? + Ch* frag_; // Includes the # + + Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_. + Allocator* ownAllocator_; //!< Allocator owned by this Uri. + }; //! GenericUri for Value (UTF-8, default allocator). -typedef GenericUri Uri; + typedef GenericUri Uri; RAPIDJSON_NAMESPACE_END diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index a835af42d..c00658c35 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -659,36 +659,37 @@ static const char kJsonIds[] = "{\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 - - EXPECT_TRUE((Pointer("/abc").GetUri(d, Pointer::UriType(doc)).Get()) == Pointer::UriType().Get()); // Out of boundary + Pointer::UriType doc("http://doc"); + Pointer::UriType root("http://doc/root/"); + Pointer::UriType empty = Pointer::UriType(); + + EXPECT_TRUE(Pointer("").GetUri(d, doc) == doc); + EXPECT_TRUE(Pointer("/foo").GetUri(d, doc) == root); + EXPECT_TRUE(Pointer("/foo/0").GetUri(d, doc) == root); + EXPECT_TRUE(Pointer("/foo/2").GetUri(d, doc) == root); + EXPECT_TRUE(Pointer("/foo/2/child").GetUri(d, doc) == Pointer::UriType("http://doc/root/inarray")); + EXPECT_TRUE(Pointer("/int").GetUri(d, doc) == root); + EXPECT_TRUE(Pointer("/str").GetUri(d, doc) == root); + EXPECT_TRUE(Pointer("/obj").GetUri(d, doc) == root); + EXPECT_TRUE(Pointer("/obj/child").GetUri(d, doc) == Pointer::UriType("http://doc/root/inobj")); + EXPECT_TRUE(Pointer("/jbo").GetUri(d, doc) == root); + EXPECT_TRUE(Pointer("/jbo/child").GetUri(d, doc) == root); // id not string + + EXPECT_TRUE(Pointer("/abc").GetUri(d, doc) == empty); // Out of boundary size_t unresolvedTokenIndex; - EXPECT_TRUE((Pointer("/foo/3").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == Pointer::UriType().Get()); // Out of boundary + EXPECT_TRUE(Pointer("/foo/3").GetUri(d, doc, &unresolvedTokenIndex) == empty); // Out of boundary EXPECT_EQ(1u, unresolvedTokenIndex); - EXPECT_TRUE((Pointer("/foo/a").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == Pointer::UriType().Get()); // "/foo" is an array, cannot query by "a" + EXPECT_TRUE(Pointer("/foo/a").GetUri(d, doc, &unresolvedTokenIndex) == empty); // "/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()) == Pointer::UriType().Get()); // "/foo/0" is an string, cannot further query + EXPECT_TRUE(Pointer("/foo/0/0").GetUri(d, doc, &unresolvedTokenIndex) == empty); // "/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()) == Pointer::UriType().Get()); // "/foo/0" is an string, cannot further query + EXPECT_TRUE(Pointer("/foo/0/a").GetUri(d, doc, &unresolvedTokenIndex) == empty); // "/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/"); + EXPECT_TRUE(Pointer(tokens, 1).GetUri(d, doc) == root); } TEST(Pointer, Get) { diff --git a/test/unittest/uritest.cpp b/test/unittest/uritest.cpp index b5eda2957..936b831ee 100644 --- a/test/unittest/uritest.cpp +++ b/test/unittest/uritest.cpp @@ -1,15 +1,15 @@ // 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. +// +// (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 // // 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 +// 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 @@ -30,247 +30,593 @@ RAPIDJSON_DIAG_OFF(4822) // local class member function does not have a body using namespace rapidjson; TEST(Uri, Parse) { - typedef std::basic_string String; - typedef GenericUri > UriType; - MemoryPoolAllocator allocator; - - String s = "http://auth/path?query#frag"; - Value v; - v.SetString(s, allocator); - UriType u = UriType(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 = UriType(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 = UriType(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 = UriType(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 = UriType(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 = UriType(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 = UriType(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 = UriType(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 = UriType(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 = UriType(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); +typedef GenericUri > UriType; +MemoryPoolAllocator allocator; +Value v; +Value w; + +v.SetString("http://auth/path/xxx?query#frag", allocator); +UriType u = UriType(v, &allocator); +EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0); +EXPECT_TRUE(StrCmp(u.GetAuthString(), "//auth") == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), "/path/xxx") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), "http://auth/path/xxx?query") == 0); +EXPECT_TRUE(StrCmp(u.GetQueryString(), "?query") == 0); +EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag") == 0); +u.Get(w, allocator); +EXPECT_TRUE(*w.GetString() == *v.GetString()); + +#if RAPIDJSON_HAS_STDSTRING +typedef std::basic_string String; +String str = "http://auth/path/xxx?query#frag"; +const UriType uri = UriType(str); +EXPECT_TRUE(UriType::GetScheme(uri) == "http:"); +EXPECT_TRUE(UriType::GetAuth(uri) == "//auth"); +EXPECT_TRUE(UriType::GetPath(uri) == "/path/xxx"); +EXPECT_TRUE(UriType::GetBase(uri) == "http://auth/path/xxx?query"); +EXPECT_TRUE(UriType::GetQuery(uri) == "?query"); +EXPECT_TRUE(UriType::GetFrag(uri) == "#frag"); +EXPECT_TRUE(UriType::Get(uri) == str); +#endif + +v.SetString("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f", allocator); +u = UriType(v); +EXPECT_TRUE(StrCmp(u.GetSchemeString(), "urn:") == 0); +EXPECT_TRUE(u.GetAuthStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), "uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); +u.Get(w, allocator); +EXPECT_TRUE(*w.GetString() == *v.GetString()); + +v.SetString("", allocator); +u = UriType(v); +EXPECT_TRUE(u.GetSchemeStringLength() == 0); +EXPECT_TRUE(u.GetAuthStringLength() == 0); +EXPECT_TRUE(u.GetPathStringLength() == 0); +EXPECT_TRUE(u.GetBaseStringLength() == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); + +v.SetString("http://auth/", allocator); +u = UriType(v); +EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0); +EXPECT_TRUE(StrCmp(u.GetAuthString(), "//auth") == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), "/") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), "http://auth/") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); + +u = UriType("/path/sub"); +EXPECT_TRUE(u.GetSchemeStringLength() == 0); +EXPECT_TRUE(u.GetAuthStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), "/path/sub") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), "/path/sub") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); + +// absolute path gets normalized +u = UriType("/path/../sub/"); +EXPECT_TRUE(u.GetSchemeStringLength() == 0); +EXPECT_TRUE(u.GetAuthStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), "/sub/") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), "/sub/") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); + +// relative path does not +u = UriType("path/../sub"); +EXPECT_TRUE(u.GetSchemeStringLength() == 0); +EXPECT_TRUE(u.GetAuthStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), "path/../sub") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), "path/../sub") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); + +u = UriType("http://auth#frag/stuff"); +EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0); +EXPECT_TRUE(StrCmp(u.GetAuthString(), "//auth") == 0); +EXPECT_TRUE(u.GetPathStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), "http://auth") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag/stuff") == 0); +EXPECT_TRUE(StrCmp(u.GetString(), "http://auth#frag/stuff") == 0); + +const Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'}; +SizeType len = internal::StrLen(c); +u = UriType(c, len); +EXPECT_TRUE(StrCmp(u.GetString(), "#frag/stuff") == 0); +EXPECT_TRUE(u.GetStringLength() == len); +EXPECT_TRUE(StrCmp(u.GetBaseString(), "") == 0); +EXPECT_TRUE(u.GetBaseStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag/stuff") == 0); +EXPECT_TRUE(u.GetFragStringLength() == len); + +u = UriType(c); +EXPECT_TRUE(StrCmp(u.GetString(), "#frag/stuff") == 0); +EXPECT_TRUE(u.GetStringLength() == len); +EXPECT_TRUE(StrCmp(u.GetBaseString(), "") == 0); +EXPECT_TRUE(u.GetBaseStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag/stuff") == 0); +EXPECT_TRUE(u.GetFragStringLength() == len); +} + +TEST(Uri, Parse_UTF16) { +typedef GenericValue > Value; +typedef GenericUri > UriType; +MemoryPoolAllocator allocator; +Value v; +Value w; + +v.SetString(L"http://auth/path/xxx?query#frag", allocator); +UriType u = UriType(v, &allocator); +EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0); +EXPECT_TRUE(StrCmp(u.GetAuthString(), L"//auth") == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), L"/path/xxx") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http://auth/path/xxx?query") == 0); +EXPECT_TRUE(StrCmp(u.GetQueryString(), L"?query") == 0); +EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag") == 0); +u.Get(w, allocator); +EXPECT_TRUE(*w.GetString() == *v.GetString()); + +#if RAPIDJSON_HAS_STDSTRING +typedef std::basic_string String; +String str = L"http://auth/path/xxx?query#frag"; +const UriType uri = UriType(str); +EXPECT_TRUE(UriType::GetScheme(uri) == L"http:"); +EXPECT_TRUE(UriType::GetAuth(uri) == L"//auth"); +EXPECT_TRUE(UriType::GetPath(uri) == L"/path/xxx"); +EXPECT_TRUE(UriType::GetBase(uri) == L"http://auth/path/xxx?query"); +EXPECT_TRUE(UriType::GetQuery(uri) == L"?query"); +EXPECT_TRUE(UriType::GetFrag(uri) == L"#frag"); +EXPECT_TRUE(UriType::Get(uri) == str); +#endif + +v.SetString(L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f", allocator); +u = UriType(v); +EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"urn:") == 0); +EXPECT_TRUE(u.GetAuthStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), L"uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); +u.Get(w, allocator); +EXPECT_TRUE(*w.GetString() == *v.GetString()); + +v.SetString(L"", allocator); +u = UriType(v); +EXPECT_TRUE(u.GetSchemeStringLength() == 0); +EXPECT_TRUE(u.GetAuthStringLength() == 0); +EXPECT_TRUE(u.GetPathStringLength() == 0); +EXPECT_TRUE(u.GetBaseStringLength() == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); + +v.SetString(L"http://auth/", allocator); +u = UriType(v); +EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0); +EXPECT_TRUE(StrCmp(u.GetAuthString(), L"//auth") == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), L"/") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http://auth/") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); + +u = UriType(L"/path/sub"); +EXPECT_TRUE(u.GetSchemeStringLength() == 0); +EXPECT_TRUE(u.GetAuthStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), L"/path/sub") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), L"/path/sub") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); + +// absolute path gets normalized +u = UriType(L"/path/../sub/"); +EXPECT_TRUE(u.GetSchemeStringLength() == 0); +EXPECT_TRUE(u.GetAuthStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), L"/sub/") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), L"/sub/") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); + +// relative path does not +u = UriType(L"path/../sub"); +EXPECT_TRUE(u.GetSchemeStringLength() == 0); +EXPECT_TRUE(u.GetAuthStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetPathString(), L"path/../sub") == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), L"path/../sub") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(u.GetFragStringLength() == 0); + +u = UriType(L"http://auth#frag/stuff"); +EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0); +EXPECT_TRUE(StrCmp(u.GetAuthString(), L"//auth") == 0); +EXPECT_TRUE(u.GetPathStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http://auth") == 0); +EXPECT_TRUE(u.GetQueryStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag/stuff") == 0); +EXPECT_TRUE(StrCmp(u.GetString(), L"http://auth#frag/stuff") == 0); + +const Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'}; +SizeType len = internal::StrLen(c); +u = UriType(c, len); +EXPECT_TRUE(StrCmp(u.GetString(), L"#frag/stuff") == 0); +EXPECT_TRUE(u.GetStringLength() == len); +EXPECT_TRUE(StrCmp(u.GetBaseString(), L"") == 0); +EXPECT_TRUE(u.GetBaseStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag/stuff") == 0); +EXPECT_TRUE(u.GetFragStringLength() == len); + +u = UriType(c); +EXPECT_TRUE(StrCmp(u.GetString(), L"#frag/stuff") == 0); +EXPECT_TRUE(u.GetStringLength() == len); +EXPECT_TRUE(StrCmp(u.GetBaseString(), L"") == 0); +EXPECT_TRUE(u.GetBaseStringLength() == 0); +EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag/stuff") == 0); +EXPECT_TRUE(u.GetFragStringLength() == len); } TEST(Uri, Resolve) { - typedef std::basic_string String; - typedef GenericUri > UriType; - - // ref is full uri - UriType base = UriType(String("http://auth/path/#frag")); - UriType ref = UriType(String("http://newauth/newpath#newfrag")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag"); - - base = UriType(String("/path/#frag")); - ref = UriType(String("http://newauth/newpath#newfrag")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag"); - - // ref is alternate uri - base = UriType(String("http://auth/path/#frag")); - ref = UriType(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 = UriType(String("http://auth/path/#")); - ref = UriType(String("/newpath#newfrag")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newpath#newfrag"); - - // ref is relative path - base = UriType(String("http://auth/path/file.json#frag")); - ref = UriType(String("newfile.json#")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#"); - - base = UriType(String("http://auth/path/file.json#frag/stuff")); - ref = UriType(String("newfile.json#newfrag/newstuff")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#newfrag/newstuff"); - - base = UriType(String("file.json")); - ref = UriType(String("newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - - base = UriType(String("file.json")); - ref = UriType(String("./newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - - base = UriType(String("file.json")); - ref = UriType(String("parent/../newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - - base = UriType(String("file.json")); - ref = UriType(String("parent/./newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "parent/newfile.json"); - - base = UriType(String("file.json")); - ref = UriType(String("../../parent/.././newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json"); - - base = UriType(String("http://auth")); - ref = UriType(String("newfile.json")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newfile.json"); - - // ref is fragment - base = UriType(String("#frag/stuff")); - ref = UriType(String("#newfrag/newstuff")); - EXPECT_TRUE(ref.Resolve(base).Get() == "#newfrag/newstuff"); - - // test ref fragment always wins - base = UriType(String("/path#frag")); - ref = UriType(String("")); - EXPECT_TRUE(ref.Resolve(base).Get() == "/path"); - - // Examples from RFC3896 - base = UriType(String("http://a/b/c/d;p?q")); - ref = UriType(String("g:h")); - EXPECT_TRUE(ref.Resolve(base).Get() == "g:h"); - ref = UriType(String("g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g"); - ref = UriType(String("./g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g"); - ref = UriType(String("g/")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g/"); - ref = UriType(String("/g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = UriType(String("//g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://g"); - ref = UriType(String("?y")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?y"); - ref = UriType(String("g?y")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y"); - ref = UriType(String("#s")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q#s"); - ref = UriType(String("g#s")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s"); - ref = UriType(String("g?y#s")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y#s"); - ref = UriType(String(";x")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/;x"); - ref = UriType(String("g;x")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x"); - ref = UriType(String("g;x?y#s")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x?y#s"); - ref = UriType(String("")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q"); - ref = UriType(String(".")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/"); - ref = UriType(String("./")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/"); - ref = UriType(String("..")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/"); - ref = UriType(String("../")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/"); - ref = UriType(String("../g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/g"); - ref = UriType(String("../..")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/"); - ref = UriType(String("../../")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/"); - ref = UriType(String("../../g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = UriType(String("../../../g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = UriType(String("../../../../g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = UriType(String("/./g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = UriType(String("/../g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g"); - ref = UriType(String("g.")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g."); - ref = UriType(String(".g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/.g"); - ref = UriType(String("g..")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g.."); - ref = UriType(String("..g")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/..g"); - ref = UriType(String("g#s/../x")); - EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s/../x"); +typedef GenericUri UriType; +CrtAllocator allocator; + +// ref is full uri +UriType base = UriType("http://auth/path/#frag"); +UriType ref = UriType("http://newauth/newpath#newfrag"); +UriType res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://newauth/newpath#newfrag") == 0); + +base = UriType("/path/#frag", &allocator); +ref = UriType("http://newauth/newpath#newfrag", &allocator); +res = ref.Resolve(base, &allocator); +EXPECT_TRUE(StrCmp(res.GetString(), "http://newauth/newpath#newfrag") == 0); + +// ref is alternate uri +base = UriType("http://auth/path/#frag"); +ref = UriType("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0); + +// ref is absolute path +base = UriType("http://auth/path/#"); +ref = UriType("/newpath#newfrag"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/newpath#newfrag") == 0); + +// ref is relative path +base = UriType("http://auth/path/file.json#frag"); +ref = UriType("newfile.json#"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/path/newfile.json#") == 0); + +base = UriType("http://auth/path/file.json#frag/stuff"); +ref = UriType("newfile.json#newfrag/newstuff"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/path/newfile.json#newfrag/newstuff") == 0); + +base = UriType("file.json", &allocator); +ref = UriType("newfile.json", &base.GetAllocator()); +res = ref.Resolve(base, &ref.GetAllocator()); +EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0); + +base = UriType("file.json", &allocator); +ref = UriType("./newfile.json", &allocator); +res = ref.Resolve(base, &allocator); +EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0); + +base = UriType("file.json"); +ref = UriType("parent/../newfile.json"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0); + +base = UriType("file.json"); +ref = UriType("parent/./newfile.json"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "parent/newfile.json") == 0); + +base = UriType("file.json"); +ref = UriType("../../parent/.././newfile.json"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0); + +// This adds a joining slash so resolved length is base length + ref length + 1 +base = UriType("http://auth"); +ref = UriType("newfile.json"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/newfile.json") == 0); + +// ref is fragment +base = UriType("#frag/stuff"); +ref = UriType("#newfrag/newstuff"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "#newfrag/newstuff") == 0); + +// test ref fragment always wins +base = UriType("/path#frag"); +ref = UriType(""); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "/path") == 0); + +// Examples from RFC3896 +base = UriType("http://a/b/c/d;p?q"); +ref = UriType("g:h"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "g:h") == 0); +ref = UriType("g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g") == 0); +ref = UriType("./g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g") == 0); +ref = UriType("g/"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g/") == 0); +ref = UriType("/g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0); +ref = UriType("//g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://g") == 0); +ref = UriType("?y"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/d;p?y") == 0); +ref = UriType("g?y"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g?y") == 0); +ref = UriType("#s"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/d;p?q#s") == 0); +ref = UriType("g#s"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g#s") == 0); +ref = UriType("g?y#s"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g?y#s") == 0); +ref = UriType(";x"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/;x") == 0); +ref = UriType("g;x"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g;x") == 0); +ref = UriType("g;x?y#s"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g;x?y#s") == 0); +ref = UriType(""); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/d;p?q") == 0); +ref = UriType("."); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/") == 0); +ref = UriType("./"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/") == 0); +ref = UriType(".."); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/") == 0); +ref = UriType("../"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/") == 0); +ref = UriType("../g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/g") == 0); +ref = UriType("../.."); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/") == 0); +ref = UriType("../../"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/") == 0); +ref = UriType("../../g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0); +ref = UriType("../../../g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0); +ref = UriType("../../../../g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0); +ref = UriType("/./g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0); +ref = UriType("/../g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0); +ref = UriType("g."); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g.") == 0); +ref = UriType(".g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/.g") == 0); +ref = UriType("g.."); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g..") == 0); +ref = UriType("..g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/..g") == 0); +ref = UriType("g#s/../x"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g#s/../x") == 0); +} + +TEST(Uri, Resolve_UTF16) { +typedef GenericValue > Value; +typedef GenericUri UriType; +CrtAllocator allocator; + +// ref is full uri +UriType base = UriType(L"http://auth/path/#frag"); +UriType ref = UriType(L"http://newauth/newpath#newfrag"); +UriType res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://newauth/newpath#newfrag") == 0); + +base = UriType(L"/path/#frag"); +ref = UriType(L"http://newauth/newpath#newfrag"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://newauth/newpath#newfrag") == 0); + +// ref is alternate uri +base = UriType(L"http://auth/path/#frag"); +ref = UriType(L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0); + +// ref is absolute path +base = UriType(L"http://auth/path/#"); +ref = UriType(L"/newpath#newfrag"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/newpath#newfrag") == 0); + +// ref is relative path +base = UriType(L"http://auth/path/file.json#frag"); +ref = UriType(L"newfile.json#"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/path/newfile.json#") == 0); + +base = UriType(L"http://auth/path/file.json#frag/stuff"); +ref = UriType(L"newfile.json#newfrag/newstuff"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/path/newfile.json#newfrag/newstuff") == 0); + +base = UriType(L"file.json", &allocator); +ref = UriType(L"newfile.json", &base.GetAllocator()); +res = ref.Resolve(base, &ref.GetAllocator()); +EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0); + +base = UriType(L"file.json", &allocator); +ref = UriType(L"./newfile.json", &allocator); +res = ref.Resolve(base, &allocator); +EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0); + +base = UriType(L"file.json"); +ref = UriType(L"parent/../newfile.json"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0); + +base = UriType(L"file.json"); +ref = UriType(L"parent/./newfile.json"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"parent/newfile.json") == 0); + +base = UriType(L"file.json"); +ref = UriType(L"../../parent/.././newfile.json"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0); + +// This adds a joining slash so resolved length is base length + ref length + 1 +base = UriType(L"http://auth"); +ref = UriType(L"newfile.json"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/newfile.json") == 0); + +// ref is fragment +base = UriType(L"#frag/stuff"); +ref = UriType(L"#newfrag/newstuff"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"#newfrag/newstuff") == 0); + +// test ref fragment always wins +base = UriType(L"/path#frag"); +ref = UriType(L""); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"/path") == 0); + +// Examples from RFC3896 +base = UriType(L"http://a/b/c/d;p?q"); +ref = UriType(L"g:h"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"g:h") == 0); +ref = UriType(L"g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g") == 0); +ref = UriType(L"./g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g") == 0); +ref = UriType(L"g/"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g/") == 0); +ref = UriType(L"/g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0); +ref = UriType(L"//g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://g") == 0); +ref = UriType(L"?y"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/d;p?y") == 0); +ref = UriType(L"g?y"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g?y") == 0); +ref = UriType(L"#s"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/d;p?q#s") == 0); +ref = UriType(L"g#s"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g#s") == 0); +ref = UriType(L"g?y#s"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g?y#s") == 0); +ref = UriType(L";x"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/;x") == 0); +ref = UriType(L"g;x"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g;x") == 0); +ref = UriType(L"g;x?y#s"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g;x?y#s") == 0); +ref = UriType(L""); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/d;p?q") == 0); +ref = UriType(L"."); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/") == 0); +ref = UriType(L"./"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/") == 0); +ref = UriType(L".."); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/") == 0); +ref = UriType(L"../"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/") == 0); +ref = UriType(L"../g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/g") == 0); +ref = UriType(L"../.."); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/") == 0); +ref = UriType(L"../../"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/") == 0); +ref = UriType(L"../../g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0); +ref = UriType(L"../../../g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0); +ref = UriType(L"../../../../g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0); +ref = UriType(L"/./g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0); +ref = UriType(L"/../g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0); +ref = UriType(L"g."); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g.") == 0); +ref = UriType(L".g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/.g") == 0); +ref = UriType(L"g.."); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g..") == 0); +ref = UriType(L"..g"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/..g") == 0); +ref = UriType(L"g#s/../x"); +res = ref.Resolve(base); +EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g#s/../x") == 0); } #if defined(_MSC_VER) || defined(__clang__) From 6d253c160d002935079503f826310f00845b290b Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Tue, 8 Jun 2021 15:31:25 +0100 Subject: [PATCH 14/14] remove compiler warning --- test/unittest/uritest.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/unittest/uritest.cpp b/test/unittest/uritest.cpp index 936b831ee..fcedcb1ab 100644 --- a/test/unittest/uritest.cpp +++ b/test/unittest/uritest.cpp @@ -143,11 +143,11 @@ EXPECT_TRUE(u.GetFragStringLength() == len); } TEST(Uri, Parse_UTF16) { -typedef GenericValue > Value; -typedef GenericUri > UriType; +typedef GenericValue > Value16; +typedef GenericUri > UriType; MemoryPoolAllocator allocator; -Value v; -Value w; +Value16 v; +Value16 w; v.SetString(L"http://auth/path/xxx?query#frag", allocator); UriType u = UriType(v, &allocator); @@ -161,7 +161,7 @@ u.Get(w, allocator); EXPECT_TRUE(*w.GetString() == *v.GetString()); #if RAPIDJSON_HAS_STDSTRING -typedef std::basic_string String; +typedef std::basic_string String; String str = L"http://auth/path/xxx?query#frag"; const UriType uri = UriType(str); EXPECT_TRUE(UriType::GetScheme(uri) == L"http:"); @@ -237,8 +237,8 @@ EXPECT_TRUE(u.GetQueryStringLength() == 0); EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag/stuff") == 0); EXPECT_TRUE(StrCmp(u.GetString(), L"http://auth#frag/stuff") == 0); -const Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'}; -SizeType len = internal::StrLen(c); +const Value16::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'}; +SizeType len = internal::StrLen(c); u = UriType(c, len); EXPECT_TRUE(StrCmp(u.GetString(), L"#frag/stuff") == 0); EXPECT_TRUE(u.GetStringLength() == len); @@ -438,8 +438,8 @@ EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g#s/../x") == 0); } TEST(Uri, Resolve_UTF16) { -typedef GenericValue > Value; -typedef GenericUri UriType; +typedef GenericValue > Value16; +typedef GenericUri UriType; CrtAllocator allocator; // ref is full uri