diff --git a/CHANGES.md b/CHANGES.md index 9fc20e904..52108db25 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,7 @@ - Fixed a bug in `SharedAssetDepot` that could cause assertion failures in debug builds, and could rarely cause premature deletion of shared assets even in release builds. - Fixed a bug that could cause `Tileset::sampleHeightMostDetailed` to return a height that is not the highest one when the sampled tileset contained multiple heights at the given location. - `LayerJsonTerrainLoader` will now log errors and warnings when failing to load a `.terrain` file referenced in the layer.json, instead of silently ignoring them. +- URIs containing unicode characters are now supported. - Fixed a crash in `CullingVolume` when the camera was very far away from the globe. ### v0.43.0 - 2025-01-02 diff --git a/CMakeLists.txt b/CMakeLists.txt index 60106534f..8fabd58b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,8 +46,8 @@ message(STATUS "VCPKG_OVERLAY_TRIPLETS ${VCPKG_OVERLAY_TRIPLETS}") # These packages are used in the public headers of Cesium libraries, so we need to distribute the headers and binaries # with the installation # Note that fmt is a public dependency of the vcpkg version of spdlog -# STB and uriparser aren't technically part of the public interface, but they're used by the downstream Cesium for Unreal project -set(PACKAGES_PUBLIC asyncplusplus expected-lite fmt glm rapidjson spdlog stb uriparser) +# STB is not technically part of the public interface, but it is used by the downstream Cesium for Unreal project +set(PACKAGES_PUBLIC asyncplusplus expected-lite fmt glm rapidjson spdlog stb ada-url) # These packages are used in the code and produce binaries, but are not part of the public interface. Therefore we need # to distribute the binaries for linking, but not the headers, as downstream consumers don't need them # OpenSSL and abseil are both dependencies of s2geometry @@ -257,8 +257,8 @@ find_package(s2 CONFIG REQUIRED) find_package(spdlog CONFIG REQUIRED) find_package(tinyxml2 CONFIG REQUIRED) find_package(unofficial-sqlite3 CONFIG REQUIRED) -find_package(uriparser CONFIG REQUIRED char wchar_t) find_package(WebP CONFIG REQUIRED) +find_package(ada CONFIG REQUIRED) # Private Library (s2geometry) diff --git a/Cesium3DTilesSelection/CMakeLists.txt b/Cesium3DTilesSelection/CMakeLists.txt index ab5d18a39..a6e1588a3 100644 --- a/Cesium3DTilesSelection/CMakeLists.txt +++ b/Cesium3DTilesSelection/CMakeLists.txt @@ -54,7 +54,6 @@ target_link_libraries(Cesium3DTilesSelection CesiumUtility spdlog::spdlog spdlog::spdlog_header_only # PRIVATE - uriparser::uriparser libmorton::libmorton draco::draco nonstd::expected-lite diff --git a/Cesium3DTilesSelection/src/LayerJsonTerrainLoader.cpp b/Cesium3DTilesSelection/src/LayerJsonTerrainLoader.cpp index 88e1c3f02..6a947acb4 100644 --- a/Cesium3DTilesSelection/src/LayerJsonTerrainLoader.cpp +++ b/Cesium3DTilesSelection/src/LayerJsonTerrainLoader.cpp @@ -340,13 +340,6 @@ Future loadLayersRecursive( std::string extensionsToRequest = createExtensionsQueryParameter(knownExtensions, extensions); - if (!extensionsToRequest.empty()) { - for (std::string& url : urls) { - url = - CesiumUtility::Uri::addQuery(url, "extensions", extensionsToRequest); - } - } - const auto availabilityLevelsIt = layerJson.FindMember("metadataAvailability"); @@ -379,6 +372,7 @@ Future loadLayersRecursive( baseUrl, std::move(version), std::move(urls), + std::move(extensionsToRequest), std::move(availability), static_cast(maxZoom), availabilityLevels); @@ -648,12 +642,14 @@ LayerJsonTerrainLoader::Layer::Layer( const std::string& baseUrl_, std::string&& version_, std::vector&& tileTemplateUrls_, + std::string&& extensionsToRequest_, CesiumGeometry::QuadtreeRectangleAvailability&& contentAvailability_, uint32_t maxZooms_, int32_t availabilityLevels_) : baseUrl{baseUrl_}, version{std::move(version_)}, tileTemplateUrls{std::move(tileTemplateUrls_)}, + extensionsToRequest{std::move(extensionsToRequest_)}, contentAvailability{std::move(contentAvailability_)}, loadedSubtrees(maxSubtreeInLayer(maxZooms_, availabilityLevels_)), availabilityLevels{availabilityLevels_} {} @@ -675,9 +671,9 @@ std::string resolveTileUrl( return std::string(); } - return CesiumUtility::Uri::resolve( + Uri uri( layer.baseUrl, - CesiumUtility::Uri::substituteTemplateParameters( + Uri::substituteTemplateParameters( layer.tileTemplateUrls[0], [&tileID, &layer](const std::string& placeholder) -> std::string { if (placeholder == "level" || placeholder == "z") { @@ -695,6 +691,14 @@ std::string resolveTileUrl( return placeholder; })); + + if (!layer.extensionsToRequest.empty()) { + UriQuery params(uri); + params.setValue("extensions", layer.extensionsToRequest); + uri.setQuery(params.toQueryString()); + } + + return uri.toString(); } Future requestTileContent( diff --git a/Cesium3DTilesSelection/src/LayerJsonTerrainLoader.h b/Cesium3DTilesSelection/src/LayerJsonTerrainLoader.h index a60900337..0f67bc596 100644 --- a/Cesium3DTilesSelection/src/LayerJsonTerrainLoader.h +++ b/Cesium3DTilesSelection/src/LayerJsonTerrainLoader.h @@ -52,6 +52,7 @@ class LayerJsonTerrainLoader : public TilesetContentLoader { const std::string& baseUrl, std::string&& version, std::vector&& tileTemplateUrls, + std::string&& extensionsToRequest, CesiumGeometry::QuadtreeRectangleAvailability&& contentAvailability, uint32_t maxZooms, int32_t availabilityLevels); @@ -59,6 +60,7 @@ class LayerJsonTerrainLoader : public TilesetContentLoader { std::string baseUrl; std::string version; std::vector tileTemplateUrls; + std::string extensionsToRequest; CesiumGeometry::QuadtreeRectangleAvailability contentAvailability; std::vector> loadedSubtrees; int32_t availabilityLevels; diff --git a/Cesium3DTilesSelection/test/TestLayerJsonTerrainLoader.cpp b/Cesium3DTilesSelection/test/TestLayerJsonTerrainLoader.cpp index cfca26fa3..d8b0877fc 100644 --- a/Cesium3DTilesSelection/test/TestLayerJsonTerrainLoader.cpp +++ b/Cesium3DTilesSelection/test/TestLayerJsonTerrainLoader.cpp @@ -270,7 +270,8 @@ TEST_CASE("Test create layer json terrain loader") { CHECK(layers[0].version == "1.33.0"); CHECK( layers[0].tileTemplateUrls.front() == - "{z}/{x}/{y}.terrain?v={version}&extensions=octvertexnormals-metadata"); + "{z}/{x}/{y}.terrain?v={version}"); + CHECK(layers[0].extensionsToRequest == "octvertexnormals-metadata"); CHECK(layers[0].loadedSubtrees.size() == 2); CHECK(layers[0].availabilityLevels == 10); } @@ -299,7 +300,8 @@ TEST_CASE("Test create layer json terrain loader") { CHECK(layers[0].version == "1.0.0"); CHECK( layers[0].tileTemplateUrls.front() == - "{z}/{x}/{y}.terrain?v={version}&extensions=octvertexnormals"); + "{z}/{x}/{y}.terrain?v={version}"); + CHECK(layers[0].extensionsToRequest == "octvertexnormals"); CHECK(layers[0].loadedSubtrees.empty()); CHECK(layers[0].availabilityLevels == -1); @@ -322,7 +324,7 @@ TEST_CASE("Test create layer json terrain loader") { auto parentJsonPath = testDataPath / "CesiumTerrainTileJson" / "Parent.tile.json"; pMockedAssetAccessor->mockCompletedRequests.insert( - {"./Parent/layer.json", createMockAssetRequest(parentJsonPath)}); + {"Parent/layer.json", createMockAssetRequest(parentJsonPath)}); auto loaderFuture = LayerJsonTerrainLoader::createLoader(externals, {}, "layer.json", {}); @@ -418,10 +420,8 @@ TEST_CASE("Test create layer json terrain loader") { const auto& layers = loaderResult.pLoader->getLayers(); CHECK(layers.size() == 1); CHECK(layers[0].tileTemplateUrls.size() == 1); - CHECK( - layers[0].tileTemplateUrls[0] == - "{z}/{x}/" - "{y}.terrain?v={version}&extensions=octvertexnormals-watermask"); + CHECK(layers[0].tileTemplateUrls[0] == "{z}/{x}/{y}.terrain?v={version}"); + CHECK(layers[0].extensionsToRequest == "octvertexnormals-watermask"); } } @@ -452,6 +452,7 @@ TEST_CASE("Test load layer json tile content") { "layer.json", "1.0.0", std::vector{"{level}.{x}.{y}/{version}.terrain"}, + "one-two", std::move(contentAvailability), maxZoom, 10); @@ -460,7 +461,7 @@ TEST_CASE("Test load layer json tile content") { // mock tile content request pMockedAssetAccessor->mockCompletedRequests.insert( - {"0.0.0/1.0.0.terrain", + {"0.0.0/1.0.0.terrain?extensions=one-two", createMockAssetRequest( testDataPath / "CesiumTerrainTileJson" / "tile.metadataavailability.terrain")}); @@ -502,6 +503,7 @@ TEST_CASE("Test load layer json tile content") { "layer.json", "1.0.0", std::vector{"{level}.{x}.{y}/{version}.terrain"}, + std::string(), std::move(contentAvailability), maxZoom, -1); @@ -563,6 +565,7 @@ TEST_CASE("Test load layer json tile content") { "layer.json", "1.0.0", std::vector{"{level}.{x}.{y}/{version}_layer0.terrain"}, + std::string(), std::move(layer0ContentAvailability), maxZoom, -1); @@ -577,6 +580,7 @@ TEST_CASE("Test load layer json tile content") { "layer.json", "1.0.0", std::vector{"{level}.{x}.{y}/{version}_layer1.terrain"}, + std::string(), std::move(layer1ContentAvailability), maxZoom, -1); @@ -639,6 +643,7 @@ TEST_CASE("Test load layer json tile content") { "layer.json", "1.0.0", std::vector{"{level}.{x}.{y}/{version}_layer0.terrain"}, + std::string(), std::move(layer0ContentAvailability), maxZoom, 10); @@ -650,6 +655,7 @@ TEST_CASE("Test load layer json tile content") { "layer.json", "1.0.0", std::vector{"{level}.{x}.{y}/{version}_layer1.terrain"}, + std::string(), std::move(layer1ContentAvailability), maxZoom, 10); @@ -724,6 +730,7 @@ TEST_CASE("Test load layer json tile content") { "layer.json", "1.0.0", std::vector{"{level}.{x}.{y}/{version}.terrain"}, + std::string{}, std::move(contentAvailability), maxZoom, 10); @@ -784,6 +791,7 @@ TEST_CASE("Test creating tile children for layer json") { "layer.json", "1.0.0", std::vector{"{level}.{x}.{y}/{version}_layer0.terrain"}, + std::string(), std::move(layer0ContentAvailability), maxZoom, 10); @@ -799,6 +807,7 @@ TEST_CASE("Test creating tile children for layer json") { "layer.json", "1.0.0", std::vector{"{level}.{x}.{y}/{version}_layer1.terrain"}, + std::string(), std::move(layer1ContentAvailability), maxZoom, 10); diff --git a/Cesium3DTilesSelection/test/data/AddTileset/tileset3/tileset3.json b/Cesium3DTilesSelection/test/data/AddTileset/tileset3/tileset3.json index 1e0cd3663..5039c78d4 100644 --- a/Cesium3DTilesSelection/test/data/AddTileset/tileset3/tileset3.json +++ b/Cesium3DTilesSelection/test/data/AddTileset/tileset3/tileset3.json @@ -17,7 +17,7 @@ "geometricError": 0, "refine": "ADD", "content": { - "uri": "tileset3/ll.b3dm" + "uri": "ll.b3dm" } } } diff --git a/CesiumIonClient/src/Connection.cpp b/CesiumIonClient/src/Connection.cpp index e7a1cc352..f1a8fddce 100644 --- a/CesiumIonClient/src/Connection.cpp +++ b/CesiumIonClient/src/Connection.cpp @@ -29,8 +29,6 @@ #include #include #include -#include -#include #include #include @@ -495,19 +493,18 @@ Asset jsonToAsset(const rapidjson::Value& item) { } std::optional generateApiUrl(const std::string& ionUrl) { - UriUriA newUri; - if (uriParseSingleUriA(&newUri, ionUrl.c_str(), nullptr) != URI_SUCCESS) { - return std::optional(); + Uri parsedIonUrl(ionUrl); + if (!parsedIonUrl) { + return std::nullopt; } - std::string hostName = - std::string(newUri.hostText.first, newUri.hostText.afterLast); - std::string scheme = - std::string(newUri.scheme.first, newUri.scheme.afterLast); - - uriFreeUriMembersA(&newUri); + std::string url; + url.append(parsedIonUrl.getScheme()); + url.append("//api."); + url.append(parsedIonUrl.getHost()); + url.append("/"); - return std::make_optional(scheme + "://api." + hostName + '/'); + return url; } } // namespace diff --git a/CesiumUtility/CMakeLists.txt b/CesiumUtility/CMakeLists.txt index edbc3a0be..ed270219a 100644 --- a/CesiumUtility/CMakeLists.txt +++ b/CesiumUtility/CMakeLists.txt @@ -46,5 +46,5 @@ target_link_libraries( zlib-ng::zlib-ng spdlog::spdlog glm::glm - uriparser::uriparser + ada::ada ) diff --git a/CesiumUtility/include/CesiumUtility/Uri.h b/CesiumUtility/include/CesiumUtility/Uri.h index c50bbb790..a57f08116 100644 --- a/CesiumUtility/include/CesiumUtility/Uri.h +++ b/CesiumUtility/include/CesiumUtility/Uri.h @@ -1,15 +1,112 @@ #pragma once +#include +#include + #include +#include #include +#include namespace CesiumUtility { + /** - * @brief A class for building and manipulating Uniform Resource Identifiers + * @brief A class for parsing and manipulating Uniform Resource Identifiers * (URIs). + * + * The URI parser supports the [WhatWG URL + * specification](https://url.spec.whatwg.org/). It also supports + * protocol-relative URIs such as `//example.com`, and opaque paths such as + * `a/b/c`. */ class Uri final { public: + /** + * @brief Attempts to create a new Uri by parsing the given string. If the + * string fails to parse, \ref isValid will return false. + * + * @param uri A string containing the URI to attempt to parse. + */ + Uri(const std::string& uri); + /** + * @brief Attempts to create a new Uri from a base URI and a relative URI. If + * the base URI is invalid, only the relative URI string will be used. If the + * relative URI fails to parse, \ref isValid will return false. + * + * @param base The base URI that the relative URI is relative to. + * @param relative A string containing a relative URI to attempt to parse. + * @param useBaseQuery If true, the resulting URI will include the query + * parameters of both the base URI and the relative URI. If false, only the + * relative URI's query parameters will be used (if any). + */ + Uri(const Uri& base, const std::string& relative, bool useBaseQuery = false); + + /** + * @brief Returns a string representation of the entire URI including path and + * query parameters. + */ + std::string toString() const; + + /** + * @brief Returns true if this URI has been successfully parsed. + */ + bool isValid() const; + + /** + * @brief Equivalent to \ref isValid. + */ + operator bool() const { return this->isValid(); } + + /** + * @brief Gets the scheme portion of the URI. If the URI was created without a + * scheme, this will return an empty string. + * + * @returns The scheme, or an empty string if the URI could not be parsed or + * has no scheme. + */ + std::string_view getScheme() const; + + /** + * @brief Gets the host portion of the URI. If the URI also specifies a + * non-default port, it will be included in the returned host. If the URI + * contains no host, this will return an empty string. + * + * @returns The host, or an empty string if the URI could not be parsed or has + * no host. + */ + std::string_view getHost() const; + + /** + * @brief Gets the path portion of the URI. This will not include query + * parameters, if present. + * + * @return The path, or empty string if the URI could not be parsed. + */ + std::string_view getPath() const; + + /** + * @brief Gets the query portion of the URI. + * + * @return The path, or empty string if the URI could not be parsed. + */ + std::string_view getQuery() const; + + /** + * @brief Sets the path portion of a URI to a new value. The other portions of + * the URI are left unmodified, including any query parameters. + * + * @param path The new path portion of the URI. + */ + void setPath(const std::string_view& path); + + /** + * @brief Sets the query portion of a URI to a new value. The other portions + * of the URI are left unmodified. + * + * @param queryString The new query portion of the URI. + */ + void setQuery(const std::string_view& queryString); + /** * @brief Attempts to resolve a relative URI using a base URI. * @@ -21,10 +118,11 @@ class Uri final { * @param relative The relative URI to be resolved against the base URI. * @param useBaseQuery If true, any query parameters of the base URI will be * retained in the resolved URI. - * @param assumeHttpsDefault If true, protocol-relative URIs (such as - * `//api.cesium.com`) will be assumed to be `https`. If false, they will be - * assumed to be `http`. + * @param assumeHttpsDefault This parameter is ignored and is only kept for + * API compatibility. * @returns The resolved URI. + * + * @deprecated Use the \ref Uri constructor instead. */ static std::string resolve( const std::string& base, @@ -40,6 +138,14 @@ class Uri final { * @param key The key to be added to the query string. * @param value The value to be added to the query string. * @returns The modified URI including the new query string parameter. + * + * @deprecated Use the \ref UriQuery class: + * ``` + * Uri parsedUri(uri); + * UriQuery params(parsedUri); + * params.setValue(key, value); + * parsedUri.setQuery(params.toQueryString()); + * ``` */ static std::string addQuery( const std::string& uri, @@ -56,6 +162,13 @@ class Uri final { * @param key The key whose value will be obtained from the URI, if possible. * @returns The value of the given key in the query string, or an empty string * if not found. + * + * @deprecated Use the \ref UriQuery class: + * ``` + * Uri parsedUri(uri); + * UriQuery params(parsedUri); + * params.getValue(key); + * ``` */ static std::string getQueryValue(const std::string& uri, const std::string& key); @@ -193,6 +306,8 @@ class Uri final { * * @param uri The URI from which to get the path. * @return The path, or empty string if the URI could not be parsed. + * + * @deprecated Create a \ref Uri instance and use \ref Uri::getPath() instead. */ static std::string getPath(const std::string& uri); @@ -204,8 +319,97 @@ class Uri final { * @param newPath The new path portion of the URI. * @returns The new URI after setting the path. If the original URI cannot be * parsed, it is returned unmodified. + * + * @deprecated Create a \ref Uri instance and use \ref Uri::setPath(const + * std::string_view&) instead. */ static std::string setPath(const std::string& uri, const std::string& newPath); + +private: + std::optional _url = std::nullopt; + bool _hasScheme = false; }; + +/** + * @brief A class for parsing and manipulating the query string of a URI. + */ +class UriQuery final { +public: + /** + * @brief Creates a \ref UriQuery object from a query string. + * + * This query string should be in the format + * `key1=value1&key2=value2&key3=value3...`. This is the format returned by + * \ref Uri::getQuery. This string can include percent-encoded values. + * + * @param queryString The query string to parse into a query params object. + */ + UriQuery(const std::string_view& queryString) : _params(queryString) {} + /** + * @brief Creates a \ref UriQuery object from a \ref Uri instance. + * + * This is equivalent to `UriQuery(uri.getQuery())`. + * + * @param uri The URI instance to obtain the query params from. + */ + UriQuery(const Uri& uri) : _params(uri.getQuery()) {} + + /** + * @brief Obtains the value of the given key from the query parameters, + * if possible. + * + * If the URI can't be parsed, or the key doesn't exist in the + * query string, `std::nullopt` will be returned. + * + * @param key The key whose value will be obtained from the query string, if + * possible. + * @returns The value of the given key in the query string, or `std::nullopt` + * if not found. + */ + std::optional getValue(const std::string& key) { + return this->_params.get(key); + } + + /** + * @brief Sets the given key in the query parameters to the given value. + * If the key doesn't exist already, it will be added to the query parameters. + * Otherwise, the previous value will be overwritten. + * + * @param key The key to be added to the query string. + * @param value The value to be added to the query string. + */ + void setValue(const std::string& key, const std::string& value) { + this->_params.set(key, value); + } + + /** + * @brief Returns true if this query string contains a value for the given + * key, or false otherwise. + * + * @param key The key to check. + */ + bool hasValue(const std::string& key) { return this->_params.has(key); } + + /** + * @brief Converts this object back into a query string, including all + * modifications that have been made. This result can be passed directly to + * \ref Uri::setQuery. + */ + std::string toQueryString() const { return this->_params.to_string(); } + + /** @brief Returns an iterator pointing to the beginning of the query + * parameters. */ + inline auto begin() const { return this->_params.begin(); } + /** @brief Returns an iterator pointing to the end of the query parameters. */ + inline auto end() const { return this->_params.end(); } + /** @brief Returns the first element in the query parameters. */ + inline auto front() const { return this->_params.front(); } + /** @brief Returns the last element in the query parameters. */ + inline auto back() const { return this->_params.back(); } + +private: + ada::url_search_params _params; +}; + } // namespace CesiumUtility diff --git a/CesiumUtility/src/Uri.cpp b/CesiumUtility/src/Uri.cpp index f311ab759..e3a7d1aa3 100644 --- a/CesiumUtility/src/Uri.cpp +++ b/CesiumUtility/src/Uri.cpp @@ -1,167 +1,189 @@ #include -#include -#include -#include +#include +#include +#include +#include -#include #include #include #include -#include +#include #include -#include +#include +#include namespace CesiumUtility { namespace { -const char* const HTTPS_PREFIX = "https:"; -const char* const HTTP_PREFIX = "http:"; +const std::string HTTPS_PREFIX = "https:"; +const std::string FILE_PREFIX = "file:///"; +const char WINDOWS_PATH_SEP = '\\'; +const char PATH_SEP = '/'; -std::string cesiumConformUrl(const std::string& url, bool useHttps) { - // Prepend protocol to protocol-relative URIs. - if (url.length() > 2 && url.at(0) == '/' && url.at(1) == '/') { - return std::string(useHttps ? HTTPS_PREFIX : HTTP_PREFIX).append(url); +using UrlResult = ada::result; + +// C++ locale settings might change which values std::isalpha checks for. We +// only want ASCII. +bool isAsciiAlpha(unsigned char c) { + return c >= 0x41 && c <= 0x7a && (c <= 0x5a || c >= 0x61); +} +bool isAscii(unsigned char c) { return c <= 0x7f; } + +/** + * A URI has a valid scheme if it starts with an ASCII alpha character and has a + * sequence of ASCII characters followed by a "://" + */ +bool urlHasScheme(const std::string& uri) { + for (size_t i = 0; i < uri.length(); i++) { + unsigned char c = static_cast(uri[i]); + if (c == ':') { + return uri.length() > i + 2 && uri[i + 1] == '/' && uri[i + 2] == '/'; + } else if ((i == 0 && !isAsciiAlpha(c)) || !isAscii(c)) { + // Scheme must start with an ASCII alpha character and be an ASCII string + return false; + } } - return url; + + return false; } } // namespace -std::string Uri::resolve( - const std::string& base, - const std::string& relative, - bool useBaseQuery, - bool assumeHttpsDefault) { - const std::string conformedBase = cesiumConformUrl(base, assumeHttpsDefault); - const std::string conformedRelative = - cesiumConformUrl(relative, assumeHttpsDefault); - UriUriA baseUri; - - if (uriParseSingleUriA(&baseUri, conformedBase.c_str(), nullptr) != - URI_SUCCESS) { - // Could not parse the base, so just use the relative directly and hope for - // the best. - return relative; +// clang-tidy isn't smart enough to understand that we *are* checking the +// optionals before accessing them, so disable the warning. +// NOLINTBEGIN(bugprone-unchecked-optional-access) +Uri::Uri(const std::string& uri) { + UrlResult result; + if (uri.starts_with("//")) { + // This is a protocol-relative URL. + // We will treat it as an HTTPS URL. + this->_hasScheme = true; + result = ada::parse(HTTPS_PREFIX + uri); + } else { + this->_hasScheme = urlHasScheme(uri); + result = this->_hasScheme ? ada::parse(uri) : ada::parse(FILE_PREFIX + uri); } - UriUriA relativeUri; - if (uriParseSingleUriA(&relativeUri, conformedRelative.c_str(), nullptr) != - URI_SUCCESS) { - // Could not parse one of the URLs, so just use the relative directly and - // hope for the best. - uriFreeUriMembersA(&baseUri); - return relative; + if (result) { + this->_url.emplace(std::move(result.value())); } +} - UriUriA resolvedUri; - if (uriAddBaseUriA(&resolvedUri, &relativeUri, &baseUri) != URI_SUCCESS) { - uriFreeUriMembersA(&resolvedUri); - uriFreeUriMembersA(&relativeUri); - uriFreeUriMembersA(&baseUri); - return relative; +Uri::Uri(const Uri& base, const std::string& relative, bool useBaseQuery) { + UrlResult result; + if (!base.isValid()) { + this->_hasScheme = urlHasScheme(relative); + result = this->_hasScheme ? ada::parse(relative) + : ada::parse(FILE_PREFIX + relative); + } else { + this->_hasScheme = base._hasScheme; + result = ada::parse(relative, &base._url.value()); } - if (uriNormalizeSyntaxA(&resolvedUri) != URI_SUCCESS) { - uriFreeUriMembersA(&resolvedUri); - uriFreeUriMembersA(&relativeUri); - uriFreeUriMembersA(&baseUri); - return relative; + if (result) { + this->_url.emplace(std::move(result.value())); + + if (useBaseQuery) { + UriQuery baseParams(base); + UriQuery relativeParams(*this); + // Set from relative to base to give priority to relative URL query string + for (const auto& [key, value] : baseParams) { + if (!relativeParams.hasValue(key)) { + relativeParams.setValue(key, value); + } + } + this->_url->set_search(relativeParams.toQueryString()); + } } +} - int charsRequired; - if (uriToStringCharsRequiredA(&resolvedUri, &charsRequired) != URI_SUCCESS) { - uriFreeUriMembersA(&resolvedUri); - uriFreeUriMembersA(&relativeUri); - uriFreeUriMembersA(&baseUri); - return relative; +std::string Uri::toString() const { + if (!this->_url) { + return ""; } - std::string result(static_cast(charsRequired), ' '); - - if (uriToStringA( - const_cast(result.c_str()), - &resolvedUri, - charsRequired + 1, - nullptr) != URI_SUCCESS) { - uriFreeUriMembersA(&resolvedUri); - uriFreeUriMembersA(&relativeUri); - uriFreeUriMembersA(&baseUri); - return relative; + const std::string_view result = this->_url->get_href(); + return this->_hasScheme ? std::string(result) + : std::string(result.substr(FILE_PREFIX.length())); +} + +bool Uri::isValid() const { return this->_url.has_value(); } + +std::string_view Uri::getScheme() const { + if (!this->isValid()) { + return {}; } - if (useBaseQuery) { - std::string query(baseUri.query.first, baseUri.query.afterLast); - if (query.length() > 0) { - if (resolvedUri.query.first) { - result += "&" + query; - } else { - result += "?" + query; - } - } + return this->_hasScheme ? this->_url->get_protocol() : std::string_view{}; +} + +std::string_view Uri::getHost() const { + if (!this->isValid()) { + return {}; } - uriFreeUriMembersA(&resolvedUri); - uriFreeUriMembersA(&relativeUri); - uriFreeUriMembersA(&baseUri); + return this->_url->get_host(); +} + +std::string_view Uri::getQuery() const { + if (!this->isValid()) { + return {}; + } - return result; + return this->_url->get_search(); +} + +std::string_view Uri::getPath() const { + if (!this->isValid()) { + return {}; + } + + // Remove leading '/' + return this->_url->get_pathname(); +} + +void Uri::setPath(const std::string_view& path) { + this->_url->set_pathname(path); +} + +void Uri::setQuery(const std::string_view& queryString) { + this->_url->set_search(queryString); +} + +std::string Uri::resolve( + const std::string& base, + const std::string& relative, + bool useBaseQuery, + [[maybe_unused]] bool assumeHttpsDefault) { + return Uri(Uri(base), relative, useBaseQuery).toString(); } std::string Uri::addQuery( const std::string& uri, const std::string& key, const std::string& value) { - // TODO - if (uri.find('?') != std::string::npos) { - return uri + "&" + key + "=" + value; + Uri parsedUri(uri); + if (!parsedUri.isValid()) { + return uri; } - return uri + "?" + key + "=" + value; - // UriUriA baseUri; - // if (uriParseSingleUriA(&baseUri, uri.c_str(), nullptr) != URI_SUCCESS) - //{ - // // TODO: report error - // return uri; - //} - - // uriFreeUriMembersA(&baseUri); + UriQuery params(parsedUri); + params.setValue(key, value); + parsedUri.setQuery(params.toQueryString()); + return parsedUri.toString(); } -std::string Uri::getQueryValue(const std::string& url, const std::string& key) { - // We need to conform the URL since it will fail parsing if it's - // protocol-relative. However, it doesn't matter what protocol we use since - // it's only extracting query parameters. - const std::string conformedUrl = cesiumConformUrl(url, true); - UriUriA uri; - if (uriParseSingleUriA(&uri, conformedUrl.c_str(), nullptr) != URI_SUCCESS) { - return ""; +std::string Uri::getQueryValue(const std::string& uri, const std::string& key) { + Uri parsedUri(uri); + if (!parsedUri.isValid()) { + return {}; } - UriQueryListA* queryList; - int itemCount; - if (uriDissectQueryMallocA( - &queryList, - &itemCount, - uri.query.first, - uri.query.afterLast) != URI_SUCCESS) { - uriFreeUriMembersA(&uri); - return ""; - } - UriQueryListA* p = queryList; - while (p) { - if (p->key && std::strcmp(p->key, key.c_str()) == 0) { - std::string value = p->value ? p->value : ""; - uriUnescapeInPlaceA(value.data()); - uriFreeQueryListA(queryList); - uriFreeUriMembersA(&uri); - return value; - } - p = p->next; - } - uriFreeQueryListA(queryList); - uriFreeUriMembersA(&uri); - return ""; + + return std::string(UriQuery(parsedUri).getValue(key).value_or("")); } +// NOLINTEND(bugprone-unchecked-optional-access) + std::string Uri::substituteTemplateParameters( const std::string& templateUri, const std::function& substitutionCallback) { @@ -179,7 +201,10 @@ std::string Uri::substituteTemplateParameters( ++nextPos; const size_t endPos = templateUri.find('}', nextPos); if (endPos == std::string::npos) { - throw std::runtime_error("Unclosed template parameter"); + // It's not a properly closed placeholder, so let's just output the rest + // of the URL (including the open brace) as-is and bail. + startPos = nextPos - 1; + break; } placeholder = templateUri.substr(nextPos, endPos - nextPos); @@ -194,240 +219,99 @@ std::string Uri::substituteTemplateParameters( } std::string Uri::escape(const std::string& s) { - // In the worst case, escaping causes each character to turn into three. - std::string result(s.size() * 3, '\0'); - char* pTerminator = uriEscapeExA( - s.data(), - s.data() + s.size(), - result.data(), - URI_FALSE, - URI_FALSE); - result.resize(size_t(pTerminator - result.data())); - return result; + return ada::unicode::percent_encode( + s, + ada::character_sets::WWW_FORM_URLENCODED_PERCENT_ENCODE); } std::string Uri::unescape(const std::string& s) { - std::string result = s; - const char* pNewNull = - uriUnescapeInPlaceExA(result.data(), URI_FALSE, URI_BR_DONT_TOUCH); - result.resize(size_t(pNewNull - result.data())); - return result; + return ada::unicode::percent_decode(s, s.find('%')); } std::string Uri::unixPathToUriPath(const std::string& unixPath) { - // UriParser docs: - // The destination buffer must be large enough to hold 7 + 3 * len(filename) - // + 1 characters in case of an absolute filename or 3 * len(filename) + 1 - // in case of a relative filename. - std::string result(7 + 3 * unixPath.size() + 1, '\0'); - if (uriUnixFilenameToUriStringA(unixPath.data(), result.data()) != 0) { - // Error - return original string. - return unixPath; - } else { - // An absolute URI will start with "file://". Remove this. - if (result.find("file://", 0, 7) != std::string::npos) { - result.erase(0, 7); - } - - // Truncate at first null character - result.resize(std::strlen(result.data())); - return result; - } + return Uri::nativePathToUriPath(unixPath); } std::string Uri::windowsPathToUriPath(const std::string& windowsPath) { - // uriWindowsFilenameToUriStringA doesn't allow `/` character in the path (it - // percent encodes them) even though that's a perfectly valid path separator - // on Windows. So convert all forward slashes to back slashes before calling - // it. - std::string windowsPathClean; - windowsPathClean.resize(windowsPath.size()); - std::replace_copy( - windowsPath.begin(), - windowsPath.end(), - windowsPathClean.begin(), - '/', - '\\'); - - // UriParser docs: - // The destination buffer must be large enough to hold 8 + 3 * len(filename) - // + 1 characters in case of an absolute filename or 3 * len(filename) + 1 - // in case of a relative filename. - std::string result(8 + 3 * windowsPathClean.size() + 1, '\0'); - - if (uriWindowsFilenameToUriStringA(windowsPathClean.data(), result.data()) != - 0) { - // Error - return original string. - return windowsPath; - } else { - // An absolute URI will start with "file://". Remove this. - if (result.find("file://", 0, 7) != std::string::npos) { - result.erase(0, 7); - } - - // Truncate at first null character - result.resize(std::strlen(result.data())); - return result; - } + return Uri::nativePathToUriPath(windowsPath); } std::string Uri::nativePathToUriPath(const std::string& nativePath) { -#ifdef _WIN32 - return windowsPathToUriPath(nativePath); -#else - return unixPathToUriPath(nativePath); -#endif + const std::string encoded = ada::unicode::percent_encode( + nativePath, + ada::character_sets::PATH_PERCENT_ENCODE); + + const bool startsWithDriveLetter = + encoded.length() >= 2 && + isAsciiAlpha(static_cast(encoded[0])) && encoded[1] == ':'; + + std::string output; + output.reserve(encoded.length() + (startsWithDriveLetter ? 1 : 0)); + + // Paths like C:/... should be prefixed with a path separator + if (startsWithDriveLetter) { + output += PATH_SEP; + } + + // All we really need to do from here is convert our slashes + for (size_t i = 0; i < encoded.length(); i++) { + if (encoded[i] == WINDOWS_PATH_SEP) { + output += PATH_SEP; + } else { + output += encoded[i]; + } + } + + return output; } std::string Uri::uriPathToUnixPath(const std::string& uriPath) { - // UriParser docs: - // The destination buffer must be large enough to hold len(uriString) + 1 - // - 5 characters in case of an absolute URI or len(uriString) + 1 in case - // of a relative URI. - // However, the above seems to assume that uriPath starts with "file:", which - // is not required. - std::string result(uriPath.size() + 1, '\0'); - if (uriUriStringToUnixFilenameA(uriPath.data(), result.data()) != 0) { - // Error - return original string. - return uriPath; - } else { - // Truncate at first null character - result.resize(std::strlen(result.data())); - return result; - } + // URI paths are pretty much just unix paths with URL encoding + const std::string_view& rawPath = uriPath; + return ada::unicode::percent_decode(rawPath, rawPath.find('%')); } std::string Uri::uriPathToWindowsPath(const std::string& uriPath) { - // If the URI starts with `/c:` or similar, remove the initial slash. - size_t skip = 0; - if (uriPath.size() >= 3 && uriPath[0] == '/' && uriPath[1] != '/' && - uriPath[2] == ':') { - skip = 1; + const std::string path = + ada::unicode::percent_decode(uriPath, uriPath.find('%')); + + size_t i = 0; + // A path including a drive name will start like /C:/.... + // In that case, we just skip the first slash and continue on + if (path.length() >= 3 && path[0] == '/' && + isAsciiAlpha(static_cast(path[1])) && path[2] == ':') { + i++; } - // UriParser docs: - // The destination buffer must be large enough to hold len(uriString) + 1 - // - 5 characters in case of an absolute URI or len(uriString) + 1 in case - // of a relative URI. - // However, the above seems to assume that uriPath starts with "file:", which - // is not required. - std::string result(uriPath.size() + 1, '\0'); - if (uriUriStringToWindowsFilenameA(uriPath.data() + skip, result.data()) != - 0) { - // Error - return original string. - return uriPath; - } else { - // Truncate at first null character - result.resize(std::strlen(result.data())); - return result; + std::string output; + output.reserve(path.length() - i); + for (; i < path.length(); i++) { + if (path[i] == PATH_SEP) { + output += WINDOWS_PATH_SEP; + } else { + output += path[i]; + } } + + return output; } -std::string Uri::uriPathToNativePath(const std::string& nativePath) { +std::string Uri::uriPathToNativePath(const std::string& uriPath) { #ifdef _WIN32 - return uriPathToWindowsPath(nativePath); + return uriPathToWindowsPath(uriPath); #else - return uriPathToUnixPath(nativePath); + return uriPathToUnixPath(uriPath); #endif } std::string Uri::getPath(const std::string& uri) { - UriUriA parsedUri; - if (uriParseSingleUriA(&parsedUri, uri.c_str(), nullptr) != URI_SUCCESS) { - // Could not parse the URI, so return an empty string. - return std::string(); - } - - // The initial string in this vector can be thought of as the "nothing" before - // the first slash in the path. - std::vector parts{std::string()}; - - UriPathSegmentA* pCurrent = parsedUri.pathHead; - while (pCurrent != nullptr) { - parts.emplace_back( - pCurrent->text.first, - size_t(pCurrent->text.afterLast - pCurrent->text.first)); - pCurrent = pCurrent->next; - } - - uriFreeUriMembersA(&parsedUri); - - return joinToString(parts, "/"); + return std::string(Uri(uri).getPath()); } std::string Uri::setPath(const std::string& uri, const std::string& newPath) { - UriUriA parsedUri; - if (uriParseSingleUriA(&parsedUri, uri.c_str(), nullptr) != URI_SUCCESS) { - // Could not parse the URI, so return an empty string. - return std::string(); - } - - // Free the existing path. Strangely, uriparser doesn't provide any simple way - // to do this. - UriPathSegmentA* pCurrent = parsedUri.pathHead; - while (pCurrent != nullptr) { - UriPathSegmentA* pNext = pCurrent->next; - free(pCurrent); - pCurrent = pNext; - } - - parsedUri.pathHead = nullptr; - parsedUri.pathTail = nullptr; - - // Set the new path. - if (!newPath.empty()) { - std::string::size_type startPos = 0; - do { - std::string::size_type nextSlashIndex = newPath.find('/', startPos); - - // Skip the initial slash if there is one. - if (nextSlashIndex == 0) { - startPos = 1; - continue; - } - - UriPathSegmentA* pSegment = - static_cast(malloc(sizeof(UriPathSegmentA))); - memset(pSegment, 0, sizeof(UriPathSegmentA)); - - if (parsedUri.pathHead == nullptr) { - parsedUri.pathHead = pSegment; - parsedUri.pathTail = pSegment; - } else { - parsedUri.pathTail->next = pSegment; - parsedUri.pathTail = parsedUri.pathTail->next; - } - - pSegment->text.first = newPath.data() + startPos; - - if (nextSlashIndex != std::string::npos) { - pSegment->text.afterLast = newPath.data() + nextSlashIndex; - startPos = nextSlashIndex + 1; - } else { - pSegment->text.afterLast = newPath.data() + newPath.size(); - startPos = nextSlashIndex; - } - } while (startPos != std::string::npos); - } - - int charsRequired; - if (uriToStringCharsRequiredA(&parsedUri, &charsRequired) != URI_SUCCESS) { - uriFreeUriMembersA(&parsedUri); - return uri; - } - - std::string result(static_cast(charsRequired), ' '); - - if (uriToStringA( - const_cast(result.c_str()), - &parsedUri, - charsRequired + 1, - nullptr) != URI_SUCCESS) { - uriFreeUriMembersA(&parsedUri); - return uri; - } - - return result; + Uri parsedUri(uri); + parsedUri.setPath(newPath); + return parsedUri.toString(); } -} // namespace CesiumUtility +} // namespace CesiumUtility \ No newline at end of file diff --git a/CesiumUtility/test/TestUri.cpp b/CesiumUtility/test/TestUri.cpp index 17cb64893..14f7631cc 100644 --- a/CesiumUtility/test/TestUri.cpp +++ b/CesiumUtility/test/TestUri.cpp @@ -2,6 +2,9 @@ #include +#include +#include + using namespace CesiumUtility; TEST_CASE("Uri::getPath") { @@ -21,23 +24,34 @@ TEST_CASE("Uri::getPath") { "/foo/bar/"); } - SUBCASE("returns empty path for nonexistent paths") { - CHECK(Uri::getPath("https://example.com") == ""); - CHECK(Uri::getPath("https://example.com?some=parameter") == ""); + SUBCASE("returns / path for nonexistent paths") { + CHECK(Uri::getPath("https://example.com") == "/"); + CHECK(Uri::getPath("https://example.com?some=parameter") == "/"); } - SUBCASE("returns empty path for invalid uri") { - CHECK(Uri::getPath("not a valid uri") == ""); + SUBCASE("handles unicode characters") { + CHECK(Uri::getPath("http://example.com/🐶.bin") == "/%F0%9F%90%B6.bin"); + CHECK( + Uri::getPath("http://example.com/示例测试用例") == + "/%E7%A4%BA%E4%BE%8B%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B"); + CHECK( + Uri::getPath("http://example.com/Ῥόδος") == + "/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82"); + CHECK( + Uri::getPath( + "http://example.com/🙍‍♂️🚪🤚/🪝🚗🚪/❓📞") == + "/%F0%9F%99%8D%E2%80%8D%E2%99%82%EF%B8%8F%F0%9F%9A%AA%F0%9F%A4%9A/" + "%F0%9F%AA%9D%F0%9F%9A%97%F0%9F%9A%AA/%E2%9D%93%F0%9F%93%9E"); } } TEST_CASE("Uri::setPath") { SUBCASE("sets empty path") { - CHECK(Uri::setPath("https://example.com", "") == "https://example.com"); + CHECK(Uri::setPath("https://example.com/", "") == "https://example.com/"); } SUBCASE("sets new path") { - CHECK(Uri::setPath("https://example.com", "/") == "https://example.com/"); + CHECK(Uri::setPath("https://example.com/", "/") == "https://example.com/"); CHECK( Uri::setPath("https://example.com/foo", "/bar") == "https://example.com/bar"); @@ -52,7 +66,7 @@ TEST_CASE("Uri::setPath") { SUBCASE("preserves path parameters") { CHECK( Uri::setPath("https://example.com?some=parameter", "") == - "https://example.com?some=parameter"); + "https://example.com/?some=parameter"); CHECK( Uri::setPath("https://example.com?some=parameter", "/") == "https://example.com/?some=parameter"); @@ -77,8 +91,14 @@ TEST_CASE("Uri::setPath") { "/foo/bar") == "https://example.com/foo/bar?some=parameter"); } - SUBCASE("returns empty path for invalid uri") { - CHECK(Uri::setPath("not a valid uri", "/foo/") == ""); + SUBCASE("handles unicode characters") { + CHECK( + Uri::setPath("http://example.com/foo/", "/🐶.bin") == + "http://example.com/%F0%9F%90%B6.bin"); + CHECK( + Uri::setPath("http://example.com/bar/", "/示例测试用例") == + "http://example.com/" + "%E7%A4%BA%E4%BE%8B%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B"); } } @@ -94,7 +114,10 @@ TEST_CASE("Uri::resolve") { "//www.example.com", "/page/test", false, - false) == "http://www.example.com/page/test"); + true) == "https://www.example.com/page/test"); + CHECK( + CesiumUtility::Uri::resolve("https://www.example.com/", "/Ῥόδος") == + "https://www.example.com/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82"); } TEST_CASE("Uri::escape") { @@ -114,7 +137,7 @@ TEST_CASE("Uri::unixPathToUriPath") { CHECK(Uri::unixPathToUriPath("wat") == "wat"); CHECK(Uri::unixPathToUriPath("wat/the") == "wat/the"); CHECK(Uri::unixPathToUriPath("/foo/bar") == "/foo/bar"); - CHECK(Uri::unixPathToUriPath("/some:file") == "/some%3Afile"); + CHECK(Uri::unixPathToUriPath("/some:file") == "/some:file"); CHECK(Uri::unixPathToUriPath("/🤞/😱/") == "/%F0%9F%A4%9E/%F0%9F%98%B1/"); } @@ -124,16 +147,16 @@ TEST_CASE("Uri::windowsPathToUriPath") { CHECK(Uri::windowsPathToUriPath("wat") == "wat"); CHECK(Uri::windowsPathToUriPath("/foo/bar") == "/foo/bar"); CHECK(Uri::windowsPathToUriPath("d:\\foo/bar\\") == "/d:/foo/bar/"); - CHECK(Uri::windowsPathToUriPath("e:\\some:file") == "/e:/some%3Afile"); + CHECK(Uri::windowsPathToUriPath("e:\\some:file") == "/e:/some:file"); CHECK( Uri::windowsPathToUriPath("c:/🤞/😱/") == "/c:/%F0%9F%A4%9E/%F0%9F%98%B1/"); CHECK( Uri::windowsPathToUriPath("notadriveletter:\\file") == - "notadriveletter%3A/file"); + "notadriveletter:/file"); CHECK( Uri::windowsPathToUriPath("\\notadriveletter:\\file") == - "/notadriveletter%3A/file"); + "/notadriveletter:/file"); } TEST_CASE("Uri::uriPathToUnixPath") { @@ -158,3 +181,58 @@ TEST_CASE("Uri::uriPathToWindowsPath") { Uri::uriPathToWindowsPath("/notadriveletter:/file") == "\\notadriveletter:\\file"); } + +TEST_CASE("Uri::addQuery") { + CHECK( + Uri::addQuery("https://example.com/", "a", "1") == + "https://example.com/?a=1"); + CHECK( + Uri::addQuery("https://example.com/?a=1", "b", "2") == + "https://example.com/?a=1&b=2"); + CHECK( + Uri::addQuery("https://example.com/?a=1", "a", "2") == + "https://example.com/?a=2"); + CHECK( + Uri::addQuery("https://unparseable url", "a", "1") == + "https://unparseable url"); + CHECK( + Uri::addQuery("https://example.com/", "a", "!@#$%^&()_+{}|") == + "https://example.com/?a=%21%40%23%24%25%5E%26%28%29_%2B%7B%7D%7C"); +} + +TEST_CASE("Uri::substituteTemplateParameters") { + const std::map params{ + {"a", "aValue"}, + {"b", "bValue"}, + {"c", "cValue"}, + {"s", "teststr"}, + {"one", "1"}}; + + const auto substitutionCallback = [¶ms](const std::string& placeholder) { + auto it = params.find(placeholder); + return it == params.end() ? placeholder : it->second; + }; + + CHECK( + Uri::substituteTemplateParameters( + "https://example.com/{a}/{b}/{c}", + substitutionCallback) == "https://example.com/aValue/bValue/cValue"); + CHECK( + Uri::substituteTemplateParameters( + "https://example.com/enco%24d%5Ee%2Fd{s}tr1n%25g", + []([[maybe_unused]] const std::string& placeholder) { + return "teststr"; + }) == "https://example.com/enco%24d%5Ee%2Fdteststrtr1n%25g"); + CHECK( + Uri::substituteTemplateParameters( + "https://example.com/{a", + substitutionCallback) == "https://example.com/{a"); + CHECK( + Uri::substituteTemplateParameters( + "https://example.com/{}", + substitutionCallback) == "https://example.com/"); + CHECK( + Uri::substituteTemplateParameters( + "https://example.com/a}", + substitutionCallback) == "https://example.com/a}"); +} \ No newline at end of file diff --git a/ThirdParty.json b/ThirdParty.json index 7616d8505..32c52a42d 100644 --- a/ThirdParty.json +++ b/ThirdParty.json @@ -1,4 +1,10 @@ [ + { + "name": "ada", + "url": "https://github.com/ada-url/ada", + "version": "2.9.2", + "license": ["Apache 2.0"] + }, { "name": "Async++", "url": "https://github.com/Amanieu/asyncplusplus", @@ -114,12 +120,6 @@ "version": "1aeb57d26bc303d5cfa1a9ff2a331df7ba278656", "license": ["zlib"] }, - { - "name": "uriparser", - "url": "https://github.com/uriparser/uriparser", - "version": "0.9.6", - "license": ["BSD-3-Clause"] - }, { "name": "zlib", "url": "https://github.com/madler/zlib", diff --git a/doc/diagrams/dependencies/Cesium3DTilesSelection.mmd b/doc/diagrams/dependencies/Cesium3DTilesSelection.mmd index 08bf7d913..88f68b564 100644 --- a/doc/diagrams/dependencies/Cesium3DTilesSelection.mmd +++ b/doc/diagrams/dependencies/Cesium3DTilesSelection.mmd @@ -23,6 +23,5 @@ graph TD Cesium3DTilesSelection[Cesium3DTilesSelection] --> spdlog_spdlog{{spdlog::spdlog}} Cesium3DTilesSelection[Cesium3DTilesSelection] --> spdlog_spdlog_header_only{{spdlog::spdlog_header_only}} Cesium3DTilesSelection[Cesium3DTilesSelection] --> tinyxml2_tinyxml2{{tinyxml2::tinyxml2}} - Cesium3DTilesSelection[Cesium3DTilesSelection] --> uriparser_uriparser{{uriparser::uriparser}} - class draco_draco,libmorton_libmorton,nonstd_expected-lite,spdlog_spdlog,spdlog_spdlog_header_only,tinyxml2_tinyxml2,uriparser_uriparser dependencyNode + class draco_draco,libmorton_libmorton,nonstd_expected-lite,spdlog_spdlog,spdlog_spdlog_header_only,tinyxml2_tinyxml2 dependencyNode class Cesium3DTiles,Cesium3DTilesContent,Cesium3DTilesReader,CesiumAsync,CesiumGeometry,CesiumGeospatial,CesiumGltf,CesiumGltfReader,CesiumQuantizedMeshTerrain,CesiumRasterOverlays,CesiumUtility,Cesium3DTilesSelection libraryNode diff --git a/doc/diagrams/dependencies/CesiumIonClient.mmd b/doc/diagrams/dependencies/CesiumIonClient.mmd index 3eef3068a..64ee8335b 100644 --- a/doc/diagrams/dependencies/CesiumIonClient.mmd +++ b/doc/diagrams/dependencies/CesiumIonClient.mmd @@ -7,10 +7,11 @@ graph TD classDef dependencyNode fill:#fff,stroke:#ccc,color:#666,font-weight:bold,font-size:28px classDef libraryNode fill:#9f9,font-weight:bold,font-size:28px CesiumIonClient[CesiumIonClient] --> CesiumAsync[CesiumAsync] + CesiumIonClient[CesiumIonClient] --> CesiumGeospatial[CesiumGeospatial] CesiumIonClient[CesiumIonClient] --> CesiumUtility[CesiumUtility] CesiumIonClient[CesiumIonClient] --> OpenSSL_Crypto{{OpenSSL::Crypto}} CesiumIonClient[CesiumIonClient] --> httplib_httplib{{httplib::httplib}} CesiumIonClient[CesiumIonClient] --> modp_b64_modp_b64{{modp_b64::modp_b64}} CesiumIonClient[CesiumIonClient] --> picosha2_picosha2{{picosha2::picosha2}} class OpenSSL_Crypto,httplib_httplib,modp_b64_modp_b64,picosha2_picosha2 dependencyNode - class CesiumAsync,CesiumUtility,CesiumIonClient libraryNode + class CesiumAsync,CesiumGeospatial,CesiumUtility,CesiumIonClient libraryNode diff --git a/doc/diagrams/dependencies/CesiumUtility.mmd b/doc/diagrams/dependencies/CesiumUtility.mmd index 4b13dfd72..7d77b1b1e 100644 --- a/doc/diagrams/dependencies/CesiumUtility.mmd +++ b/doc/diagrams/dependencies/CesiumUtility.mmd @@ -6,9 +6,9 @@ title: CesiumUtility Dependency Graph graph TD classDef dependencyNode fill:#fff,stroke:#ccc,color:#666,font-weight:bold,font-size:28px classDef libraryNode fill:#9f9,font-weight:bold,font-size:28px + CesiumUtility[CesiumUtility] --> ada_ada{{ada::ada}} CesiumUtility[CesiumUtility] --> glm_glm{{glm::glm}} CesiumUtility[CesiumUtility] --> spdlog_spdlog{{spdlog::spdlog}} - CesiumUtility[CesiumUtility] --> uriparser_uriparser{{uriparser::uriparser}} CesiumUtility[CesiumUtility] --> zlib-ng_zlib-ng{{zlib-ng::zlib-ng}} - class glm_glm,spdlog_spdlog,uriparser_uriparser,zlib-ng_zlib-ng dependencyNode + class ada_ada,glm_glm,spdlog_spdlog,zlib-ng_zlib-ng dependencyNode class CesiumUtility libraryNode diff --git a/doc/diagrams/dependencies/all.mmd b/doc/diagrams/dependencies/all.mmd index 339f85764..c24fc8f01 100644 --- a/doc/diagrams/dependencies/all.mmd +++ b/doc/diagrams/dependencies/all.mmd @@ -7,9 +7,9 @@ config: graph TD classDef dependencyNode fill:#fff,stroke:#ccc,color:#666,font-weight:bold,font-size:28px classDef libraryNode fill:#9f9,font-weight:bold,font-size:28px + CesiumUtility[CesiumUtility] --> ada_ada{{ada::ada}} CesiumUtility[CesiumUtility] --> glm_glm{{glm::glm}} CesiumUtility[CesiumUtility] --> spdlog_spdlog{{spdlog::spdlog}} - CesiumUtility[CesiumUtility] --> uriparser_uriparser{{uriparser::uriparser}} CesiumUtility[CesiumUtility] --> zlib-ng_zlib-ng{{zlib-ng::zlib-ng}} Cesium3DTiles[Cesium3DTiles] --> CesiumUtility[CesiumUtility] Cesium3DTilesContent[Cesium3DTilesContent] --> Cesium3DTiles[Cesium3DTiles] @@ -70,7 +70,6 @@ graph TD Cesium3DTilesSelection[Cesium3DTilesSelection] --> spdlog_spdlog{{spdlog::spdlog}} Cesium3DTilesSelection[Cesium3DTilesSelection] --> spdlog_spdlog_header_only{{spdlog::spdlog_header_only}} Cesium3DTilesSelection[Cesium3DTilesSelection] --> tinyxml2_tinyxml2{{tinyxml2::tinyxml2}} - Cesium3DTilesSelection[Cesium3DTilesSelection] --> uriparser_uriparser{{uriparser::uriparser}} CesiumQuantizedMeshTerrain[CesiumQuantizedMeshTerrain] --> CesiumAsync[CesiumAsync] CesiumQuantizedMeshTerrain[CesiumQuantizedMeshTerrain] --> CesiumGeospatial[CesiumGeospatial] CesiumQuantizedMeshTerrain[CesiumQuantizedMeshTerrain] --> CesiumGltf[CesiumGltf] @@ -94,28 +93,29 @@ graph TD CesiumGltfWriter[CesiumGltfWriter] --> CesiumJsonWriter[CesiumJsonWriter] CesiumGltfWriter[CesiumGltfWriter] --> modp_b64_modp_b64{{modp_b64::modp_b64}} CesiumIonClient[CesiumIonClient] --> CesiumAsync[CesiumAsync] + CesiumIonClient[CesiumIonClient] --> CesiumGeospatial[CesiumGeospatial] CesiumIonClient[CesiumIonClient] --> CesiumUtility[CesiumUtility] CesiumIonClient[CesiumIonClient] --> OpenSSL_Crypto{{OpenSSL::Crypto}} CesiumIonClient[CesiumIonClient] --> httplib_httplib{{httplib::httplib}} CesiumIonClient[CesiumIonClient] --> modp_b64_modp_b64{{modp_b64::modp_b64}} CesiumIonClient[CesiumIonClient] --> picosha2_picosha2{{picosha2::picosha2}} - class glm_glm,spdlog_spdlog,uriparser_uriparser,zlib-ng_zlib-ng,libmorton_libmorton,Async_,spdlog_spdlog_header_only,unofficial_sqlite3_sqlite3,earcut,s2_s2,KTX_ktx,WebP_webp,WebP_webpdecoder,draco_draco,libjpeg-turbo_turbojpeg-static,meshoptimizer_meshoptimizer,modp_b64_modp_b64,nonstd_expected-lite,tinyxml2_tinyxml2,OpenSSL_Crypto,httplib_httplib,picosha2_picosha2 dependencyNode + class ada_ada,glm_glm,spdlog_spdlog,zlib-ng_zlib-ng,libmorton_libmorton,Async_,spdlog_spdlog_header_only,unofficial_sqlite3_sqlite3,earcut,s2_s2,KTX_ktx,WebP_webp,WebP_webpdecoder,draco_draco,libjpeg-turbo_turbojpeg-static,meshoptimizer_meshoptimizer,modp_b64_modp_b64,nonstd_expected-lite,tinyxml2_tinyxml2,OpenSSL_Crypto,httplib_httplib,picosha2_picosha2 dependencyNode class CesiumUtility,Cesium3DTiles,Cesium3DTilesReader,CesiumAsync,CesiumGeometry,CesiumGeospatial,CesiumGltf,CesiumGltfContent,CesiumGltfReader,Cesium3DTilesContent,CesiumJsonReader,CesiumQuantizedMeshTerrain,CesiumRasterOverlays,Cesium3DTilesSelection,CesiumJsonWriter,Cesium3DTilesWriter,CesiumGltfWriter,CesiumIonClient libraryNode linkStyle 0 stroke:#ff0029,stroke-width:8px - linkStyle 1,20,60 stroke:#377eb8,stroke-width:8px - linkStyle 2,63 stroke:#66a61e,stroke-width:8px + linkStyle 1 stroke:#377eb8,stroke-width:8px + linkStyle 2,20,60 stroke:#66a61e,stroke-width:8px linkStyle 3 stroke:#984ea3,stroke-width:8px - linkStyle 4,13,19,23,24,26,29,35,56,70,71,78,87 stroke:#00d2d5,stroke-width:8px - linkStyle 5,15,46,81 stroke:#ff7f00,stroke-width:8px + linkStyle 4,13,19,23,24,26,29,35,56,69,70,77,87 stroke:#00d2d5,stroke-width:8px + linkStyle 5,15,46,80 stroke:#ff7f00,stroke-width:8px linkStyle 6,48 stroke:#af8d00,stroke-width:8px - linkStyle 7,16,30,36,49,64,72,86 stroke:#7f80cd,stroke-width:8px - linkStyle 8,25,31,50,73 stroke:#b3e900,stroke-width:8px - linkStyle 9,32,51,65,74 stroke:#c42e60,stroke-width:8px - linkStyle 10,33,37,52,66,75,83 stroke:#a65628,stroke-width:8px - linkStyle 11,67,76 stroke:#f781bf,stroke-width:8px - linkStyle 12,34,53,77 stroke:#8dd3c7,stroke-width:8px + linkStyle 7,16,30,36,49,63,71,85 stroke:#7f80cd,stroke-width:8px + linkStyle 8,25,31,50,72 stroke:#b3e900,stroke-width:8px + linkStyle 9,32,51,64,73,86 stroke:#c42e60,stroke-width:8px + linkStyle 10,33,37,52,65,74,82 stroke:#a65628,stroke-width:8px + linkStyle 11,66,75 stroke:#f781bf,stroke-width:8px + linkStyle 12,34,53,76 stroke:#8dd3c7,stroke-width:8px linkStyle 14,58 stroke:#bebada,stroke-width:8px - linkStyle 17,38,68 stroke:#fb8072,stroke-width:8px + linkStyle 17,38,67 stroke:#fb8072,stroke-width:8px linkStyle 18 stroke:#80b1d3,stroke-width:8px linkStyle 21,61 stroke:#fdb462,stroke-width:8px linkStyle 22 stroke:#fccde5,stroke-width:8px @@ -127,13 +127,13 @@ graph TD linkStyle 42,57 stroke:#d95f02,stroke-width:8px linkStyle 43 stroke:#e7298a,stroke-width:8px linkStyle 44 stroke:#e6ab02,stroke-width:8px - linkStyle 45,85,90 stroke:#a6761d,stroke-width:8px + linkStyle 45,84,90 stroke:#a6761d,stroke-width:8px linkStyle 47 stroke:#0097ff,stroke-width:8px linkStyle 54 stroke:#00d067,stroke-width:8px linkStyle 55 stroke:#000000,stroke-width:8px - linkStyle 59,79 stroke:#252525,stroke-width:8px - linkStyle 62,80 stroke:#525252,stroke-width:8px - linkStyle 69,82,84 stroke:#737373,stroke-width:8px + linkStyle 59,78 stroke:#252525,stroke-width:8px + linkStyle 62,79 stroke:#525252,stroke-width:8px + linkStyle 68,81,83 stroke:#737373,stroke-width:8px linkStyle 88 stroke:#969696,stroke-width:8px linkStyle 89 stroke:#bdbdbd,stroke-width:8px linkStyle 91 stroke:#f43600,stroke-width:8px diff --git a/doc/img/dependency-graph.svg b/doc/img/dependency-graph.svg index b34ba7aa5..18fe90b27 100644 --- a/doc/img/dependency-graph.svg +++ b/doc/img/dependency-graph.svg @@ -1 +1 @@ -

CesiumUtility

glm::glm

spdlog::spdlog

uriparser::uriparser

zlib-ng::zlib-ng

Cesium3DTiles

Cesium3DTilesContent

Cesium3DTilesReader

CesiumAsync

CesiumGeometry

CesiumGeospatial

CesiumGltf

CesiumGltfContent

CesiumGltfReader

libmorton::libmorton

CesiumJsonReader

Async++

spdlog::spdlog_header_only

unofficial::sqlite3::sqlite3

earcut

s2::s2

KTX::ktx

WebP::webp

WebP::webpdecoder

draco::draco

libjpeg-turbo::turbojpeg-static

meshoptimizer::meshoptimizer

modp_b64::modp_b64

Cesium3DTilesSelection

CesiumQuantizedMeshTerrain

CesiumRasterOverlays

nonstd::expected-lite

tinyxml2::tinyxml2

CesiumJsonWriter

Cesium3DTilesWriter

CesiumGltfWriter

CesiumIonClient

OpenSSL::Crypto

httplib::httplib

picosha2::picosha2

\ No newline at end of file +

CesiumUtility

ada::ada

glm::glm

spdlog::spdlog

zlib-ng::zlib-ng

Cesium3DTiles

Cesium3DTilesContent

Cesium3DTilesReader

CesiumAsync

CesiumGeometry

CesiumGeospatial

CesiumGltf

CesiumGltfContent

CesiumGltfReader

libmorton::libmorton

CesiumJsonReader

Async++

spdlog::spdlog_header_only

unofficial::sqlite3::sqlite3

earcut

s2::s2

KTX::ktx

WebP::webp

WebP::webpdecoder

draco::draco

libjpeg-turbo::turbojpeg-static

meshoptimizer::meshoptimizer

modp_b64::modp_b64

Cesium3DTilesSelection

CesiumQuantizedMeshTerrain

CesiumRasterOverlays

nonstd::expected-lite

tinyxml2::tinyxml2

CesiumJsonWriter

Cesium3DTilesWriter

CesiumGltfWriter

CesiumIonClient

OpenSSL::Crypto

httplib::httplib

picosha2::picosha2

\ No newline at end of file diff --git a/doc/topics/dependencies.md b/doc/topics/dependencies.md index b3b418f23..9c026b04b 100644 --- a/doc/topics/dependencies.md +++ b/doc/topics/dependencies.md @@ -4,6 +4,7 @@ Cesium Native relies on a number of third-party dependencies. These dependencies | Dependency | Usage | | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| [ada](https://github.com/ada-url/ada) | Used to parse and manipulate URIs. | | [Async++](https://github.com/Amanieu/asyncplusplus) | Used by CesiumAsync for cross-platform concurrency. | | [Catch2](https://github.com/catchorg/Catch2) | Test framework used by CesiumNativeTests. | | [draco](https://github.com/google/draco) | Required to decode meshes and point clouds compressed with Draco. | @@ -25,7 +26,6 @@ Cesium Native relies on a number of third-party dependencies. These dependencies | [sqlite3](https://www.sqlite.org/index.html) | Used to cache HTTP responses. | | [stb_image](https://github.com/nothings/stb/blob/master/stb_image.h) | A simple image loader. | | [tinyxml2](https://github.com/leethomason/tinyxml2) | XML parser for interacting with XML APIs such as those implementing the Web Map Service standard. | -| [uriparser](https://github.com/uriparser/uriparser) | Used to parse and manipulate URIs. | | [zlib-ng](https://github.com/zlib-ng/zlib-ng) | An optimized zlib implementation for working with Gzipped data. | The following chart illustrates the connections between the Cesium Native libraries and third-party dependencies: