mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-30 09:35:40 +00:00
482 lines
19 KiB
C++
482 lines
19 KiB
C++
// Tencent is pleased to support the open source community by making RapidJSON available.
|
|
//
|
|
// (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
|
|
// 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)
|
|
#elif defined(_MSC_VER)
|
|
RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated
|
|
#endif
|
|
|
|
RAPIDJSON_NAMESPACE_BEGIN
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// GenericUri
|
|
|
|
template <typename ValueType, typename Allocator=CrtAllocator>
|
|
class GenericUri {
|
|
public:
|
|
typedef typename ValueType::Ch Ch;
|
|
#if RAPIDJSON_HAS_STDSTRING
|
|
typedef std::basic_string<Ch> String;
|
|
#endif
|
|
|
|
//! Constructors
|
|
GenericUri(Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
|
|
}
|
|
|
|
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, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
|
|
Parse(uri, internal::StrLen<Ch>(uri));
|
|
}
|
|
|
|
// Use with specializations of GenericValue
|
|
template<typename T> GenericUri(const T& uri, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
|
|
const Ch* u = uri.template Get<const Ch*>(); // TypeHelper from document.h
|
|
Parse(u, internal::StrLen<Ch>(u));
|
|
}
|
|
|
|
#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<Ch>(uri.c_str()));
|
|
}
|
|
#endif
|
|
|
|
//! Copy constructor
|
|
GenericUri(const GenericUri& rhs) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(), ownAllocator_() {
|
|
*this = rhs;
|
|
}
|
|
|
|
//! Copy constructor
|
|
GenericUri(const GenericUri& rhs, Allocator* allocator) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
|
|
*this = rhs;
|
|
}
|
|
|
|
//! Destructor.
|
|
~GenericUri() {
|
|
Free();
|
|
RAPIDJSON_DELETE(ownAllocator_);
|
|
}
|
|
|
|
//! Assignment operator
|
|
GenericUri& operator=(const GenericUri& rhs) {
|
|
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());
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
//! Getters
|
|
// Use with specializations of GenericValue
|
|
template<typename T> void Get(T& uri, Allocator& allocator) {
|
|
uri.template Set<const Ch*>(this->GetString(), allocator); // TypeHelper from document.h
|
|
}
|
|
|
|
const Ch* GetString() const { return uri_; }
|
|
SizeType GetStringLength() const { return uri_ == 0 ? 0 : internal::StrLen<Ch>(uri_); }
|
|
const Ch* GetBaseString() const { return base_; }
|
|
SizeType GetBaseStringLength() const { return base_ == 0 ? 0 : internal::StrLen<Ch>(base_); }
|
|
const Ch* GetSchemeString() const { return scheme_; }
|
|
SizeType GetSchemeStringLength() const { return scheme_ == 0 ? 0 : internal::StrLen<Ch>(scheme_); }
|
|
const Ch* GetAuthString() const { return auth_; }
|
|
SizeType GetAuthStringLength() const { return auth_ == 0 ? 0 : internal::StrLen<Ch>(auth_); }
|
|
const Ch* GetPathString() const { return path_; }
|
|
SizeType GetPathStringLength() const { return path_ == 0 ? 0 : internal::StrLen<Ch>(path_); }
|
|
const Ch* GetQueryString() const { return query_; }
|
|
SizeType GetQueryStringLength() const { return query_ == 0 ? 0 : internal::StrLen<Ch>(query_); }
|
|
const Ch* GetFragString() const { return frag_; }
|
|
SizeType GetFragStringLength() const { return frag_ == 0 ? 0 : internal::StrLen<Ch>(frag_); }
|
|
|
|
#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
|
|
|
|
//! Equality operators
|
|
bool operator==(const GenericUri& rhs) const {
|
|
return Match(rhs, true);
|
|
}
|
|
|
|
bool operator!=(const GenericUri& rhs) const {
|
|
return !Match(rhs, true);
|
|
}
|
|
|
|
bool Match(const GenericUri& uri, bool full = true) 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<Ch>(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.
|
|
// Returns a new GenericUri for the resolved URI.
|
|
GenericUri Resolve(const GenericUri& baseuri, Allocator* allocator = 0) {
|
|
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
|
|
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 {
|
|
// 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 {
|
|
// 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 {
|
|
if (path_[0] == '/') {
|
|
// Absolute path - use all of this path
|
|
resuri.query_ = CopyPart(resuri.path_, path_, GetPathStringLength());
|
|
resuri.RemoveDotSegments();
|
|
} else {
|
|
// 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());
|
|
}
|
|
}
|
|
}
|
|
// Always use this frag
|
|
resuri.base_ = CopyPart(resuri.frag_, frag_, GetFragStringLength());
|
|
|
|
// Re-constitute base_ and uri_
|
|
resuri.SetBase();
|
|
resuri.uri_ = resuri.base_ + resuri.GetBaseStringLength() + 1;
|
|
resuri.SetUri();
|
|
return resuri;
|
|
}
|
|
|
|
//! 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) {
|
|
// Create own allocator if user did not supply.
|
|
if (!allocator_)
|
|
ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)();
|
|
|
|
// 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
|
|
// Note need to set, increment, assign in 3 stages to avoid compiler warning bug.
|
|
size_t total = (3 * len + 7) * sizeof(Ch);
|
|
scheme_ = static_cast<Ch*>(allocator_->Malloc(total));
|
|
*scheme_ = '\0';
|
|
auth_ = scheme_;
|
|
auth_++;
|
|
*auth_ = '\0';
|
|
path_ = auth_;
|
|
path_++;
|
|
*path_ = '\0';
|
|
query_ = path_;
|
|
query_++;
|
|
*query_ = '\0';
|
|
frag_ = query_;
|
|
frag_++;
|
|
*frag_ = '\0';
|
|
base_ = frag_;
|
|
base_++;
|
|
*base_ = '\0';
|
|
uri_ = base_;
|
|
uri_++;
|
|
*uri_ = '\0';
|
|
return total;
|
|
}
|
|
|
|
// Free memory for a URI
|
|
void Free() {
|
|
if (scheme_) {
|
|
Allocator::Free(scheme_);
|
|
scheme_ = 0;
|
|
}
|
|
}
|
|
|
|
// 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 Ch* uri, std::size_t len) {
|
|
std::size_t start = 0, pos1 = 0, pos2 = 0;
|
|
Allocate(len);
|
|
|
|
// Look for scheme ([^:/?#]+):)?
|
|
if (start < len) {
|
|
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++;
|
|
std::memcpy(scheme_, &uri[start], pos1 * sizeof(Ch));
|
|
scheme_[pos1] = '\0';
|
|
start = pos1;
|
|
}
|
|
}
|
|
}
|
|
// Look for auth (//([^/?#]*))?
|
|
// Note need to set, increment, assign in 3 stages to avoid compiler warning bug.
|
|
auth_ = scheme_ + GetSchemeStringLength();
|
|
auth_++;
|
|
*auth_ = '\0';
|
|
if (start < len - 1 && uri[start] == '/' && uri[start + 1] == '/') {
|
|
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 ([^?#]*)
|
|
// Note need to set, increment, assign in 3 stages to avoid compiler warning bug.
|
|
path_ = auth_ + GetAuthStringLength();
|
|
path_++;
|
|
*path_ = '\0';
|
|
if (start < len) {
|
|
pos2 = start;
|
|
while (pos2 < len) {
|
|
if (uri[pos2] == '?') break;
|
|
if (uri[pos2] == '#') break;
|
|
pos2++;
|
|
}
|
|
if (start != pos2) {
|
|
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 (\?([^#]*))?
|
|
// Note need to set, increment, assign in 3 stages to avoid compiler warning bug.
|
|
query_ = path_ + GetPathStringLength();
|
|
query_++;
|
|
*query_ = '\0';
|
|
if (start < len && uri[start] == '?') {
|
|
pos2 = start + 1;
|
|
while (pos2 < len) {
|
|
if (uri[pos2] == '#') break;
|
|
pos2++;
|
|
}
|
|
if (start != pos2) {
|
|
std::memcpy(query_, &uri[start], (pos2 - start) * sizeof(Ch));
|
|
query_[pos2 - start] = '\0';
|
|
start = pos2;
|
|
}
|
|
}
|
|
// Look for fragment (#(.*))?
|
|
// Note need to set, increment, assign in 3 stages to avoid compiler warning bug.
|
|
frag_ = query_ + GetQueryStringLength();
|
|
frag_++;
|
|
*frag_ = '\0';
|
|
if (start < len && uri[start] == '#') {
|
|
std::memcpy(frag_, &uri[start], (len - start) * sizeof(Ch));
|
|
frag_[len - start] = '\0';
|
|
}
|
|
|
|
// Re-constitute base_ and uri_
|
|
base_ = frag_ + GetFragStringLength() + 1;
|
|
SetBase();
|
|
uri_ = base_ + GetBaseStringLength() + 1;
|
|
SetUri();
|
|
}
|
|
|
|
// 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 the path_ member.
|
|
// https://tools.ietf.org/html/rfc3986
|
|
// 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
|
|
RAPIDJSON_ASSERT(newpos == 0 || path_[newpos - 1] == '/');
|
|
size_t lastslashpos = newpos;
|
|
// Make sure we don't go beyond the start segment
|
|
if (lastslashpos > 1) {
|
|
// Find the next to last slash and back up to it
|
|
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
|
|
} else {
|
|
// 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
|
|
pathpos += slashpos + 1;
|
|
}
|
|
path_[newpos] = '\0';
|
|
}
|
|
|
|
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<Value> Uri;
|
|
|
|
RAPIDJSON_NAMESPACE_END
|
|
|
|
#if defined(__clang__)
|
|
RAPIDJSON_DIAG_POP
|
|
#endif
|
|
|
|
#endif // RAPIDJSON_URI_H_
|