diff --git a/src/libs/libNet.cpp b/src/libs/libNet.cpp index 59d4c97b..c9cebc60 100644 --- a/src/libs/libNet.cpp +++ b/src/libs/libNet.cpp @@ -599,34 +599,66 @@ static int KYTY_SYSV_ABI HttpUriBuild(char* out, size_t* require, size_t prepare return HTTP_ERROR_INVALID_VALUE; } - std::string uri; - if (src_element->scheme != nullptr) { - uri.append(src_element->scheme); - uri.push_back(':'); - } + constexpr uint32_t BUILD_WITH_SCHEME = 0x01; + constexpr uint32_t BUILD_WITH_HOSTNAME = 0x02; + constexpr uint32_t BUILD_WITH_PORT = 0x04; + constexpr uint32_t BUILD_WITH_PATH = 0x08; + constexpr uint32_t BUILD_WITH_USERNAME = 0x10; + constexpr uint32_t BUILD_WITH_PASSWORD = 0x20; + constexpr uint32_t BUILD_WITH_QUERY = 0x40; + constexpr uint32_t BUILD_WITH_FRAGMENT = 0x80; + constexpr uint32_t BUILD_WITH_ALL = 0xff; - if (src_element->opaque == 0) { - if (src_element->hostname != nullptr) { + // No component bits preserves the legacy full-URI build. + const uint32_t parts = (option & BUILD_WITH_ALL) == 0 ? BUILD_WITH_ALL : option; + const bool hierarchical = src_element->opaque == 0 && src_element->hostname != nullptr; + const bool with_hostname = (parts & BUILD_WITH_HOSTNAME) != 0 && hierarchical; + + std::string uri; + if ((parts & BUILD_WITH_SCHEME) != 0) { + if (src_element->scheme != nullptr) { + uri.append(src_element->scheme); + uri.push_back(':'); + } + // Scheme-only builds include the authority marker. + if (hierarchical) { uri.append("//"); - if (src_element->username != nullptr) { - uri.append(src_element->username); - if (src_element->password != nullptr) { - uri.push_back(':'); - uri.append(src_element->password); - } - uri.push_back('@'); - } - uri.append(src_element->hostname); - if (src_element->port != 0) { - uri.push_back(':'); - uri.append(std::to_string(src_element->port)); - } } } - AppendUriPart(&uri, src_element->path); - AppendUriPart(&uri, src_element->query); - AppendUriPart(&uri, src_element->fragment); + const bool with_username = (parts & BUILD_WITH_USERNAME) != 0 && src_element->username != nullptr; + const bool with_password = (parts & BUILD_WITH_PASSWORD) != 0 && src_element->password != nullptr; + if (with_username) { + uri.append(src_element->username); + } + if (with_password) { + if (with_username) { + uri.push_back(':'); + } + uri.append(src_element->password); + } + if (with_hostname) { + if (with_username || with_password) { + uri.push_back('@'); + } + uri.append(src_element->hostname); + } + if ((parts & BUILD_WITH_PORT) != 0 && hierarchical && src_element->port != 0) { + if (with_hostname) { + uri.push_back(':'); + } + uri.append(std::to_string(src_element->port)); + } + + if ((parts & BUILD_WITH_PATH) != 0) { + AppendUriPart(&uri, src_element->path); + } + if ((parts & BUILD_WITH_QUERY) != 0) { + AppendUriPart(&uri, src_element->query); + } + if ((parts & BUILD_WITH_FRAGMENT) != 0) { + AppendUriPart(&uri, src_element->fragment); + } const auto needed = uri.size() + 1; if (require != nullptr) { diff --git a/tests/HttpUriParseTests.cpp b/tests/HttpUriParseTests.cpp index ac537d63..819e88f1 100644 --- a/tests/HttpUriParseTests.cpp +++ b/tests/HttpUriParseTests.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace Libs::LibHttp { void InitNet_1_Http(Loader::SymbolDatabase *symbols); @@ -40,14 +41,14 @@ int failures = 0; using HttpUriParse = int(KYTY_SYSV_ABI *)(SceHttpUriElement *, const char *, void *, size_t *, size_t); -HttpUriParse GetHttpUriParse() { - Loader::SymbolDatabase symbols; - Libs::LibHttp::InitNet_1_Http(&symbols); - const auto *record = - symbols.FindByNid("IWalAn-guFs", Loader::SymbolType::Func); +using HttpUriBuild = int(KYTY_SYSV_ABI *)(char *, size_t *, size_t, + const SceHttpUriElement *, uint32_t); + +template +T GetHttpFunction(const Loader::SymbolDatabase &symbols, const char *nid) { + const auto *record = symbols.FindByNid(nid, Loader::SymbolType::Func); CHECK(record != nullptr); - return record != nullptr ? reinterpret_cast(record->vaddr) - : nullptr; + return record != nullptr ? reinterpret_cast(record->vaddr) : nullptr; } template @@ -117,13 +118,61 @@ void TestPresentQuery(HttpUriParse parse) { } } +std::string BuildWith(HttpUriBuild build, const SceHttpUriElement &element, + uint32_t option) { + size_t required = 0; + CHECK(build(nullptr, &required, 0, &element, option) == 0); + std::array out{}; + size_t filled = 0; + CHECK(required <= out.size()); + CHECK(build(out.data(), &filled, required, &element, option) == 0); + CHECK(filled == required); + return std::string(out.data()); +} + +void TestBuildHonoursOption(HttpUriParse parse, HttpUriBuild build) { + constexpr char url[] = + "https://user:pw@gssdk1.gamesci.com.cn:8443/VersionServerImpl?x=1#frag"; + size_t required = 0; + CHECK(parse(nullptr, url, nullptr, &required, 0) == 0); + std::array pool{}; + SceHttpUriElement element{}; + CHECK(parse(&element, url, pool.data(), &required, required) == 0); + + constexpr uint32_t scheme = 0x01, hostname = 0x02, port = 0x04, path = 0x08, + username = 0x10, password = 0x20, query = 0x40, + fragment = 0x80; + + CHECK(BuildWith(build, element, scheme) == "https://"); + CHECK(BuildWith(build, element, hostname) == "gssdk1.gamesci.com.cn"); + CHECK(BuildWith(build, element, port) == "8443"); + CHECK(BuildWith(build, element, path) == "/VersionServerImpl"); + CHECK(BuildWith(build, element, username) == "user"); + CHECK(BuildWith(build, element, password) == "pw"); + CHECK(BuildWith(build, element, query) == "?x=1"); + CHECK(BuildWith(build, element, fragment) == "#frag"); + CHECK(BuildWith(build, element, scheme | hostname) == + "https://gssdk1.gamesci.com.cn"); + CHECK(BuildWith(build, element, hostname | path) == + "gssdk1.gamesci.com.cn/VersionServerImpl"); + CHECK(BuildWith(build, element, hostname | port) == + "gssdk1.gamesci.com.cn:8443"); + + CHECK(BuildWith(build, element, 0xff) == url); + CHECK(BuildWith(build, element, 0) == url); +} + } // namespace int main() { - const auto parse = GetHttpUriParse(); - if (parse == nullptr) { + Loader::SymbolDatabase symbols; + Libs::LibHttp::InitNet_1_Http(&symbols); + const auto parse = GetHttpFunction(symbols, "IWalAn-guFs"); + const auto build = GetHttpFunction(symbols, "5LZA+KPISVA"); + if (parse == nullptr || build == nullptr) { return 1; } + TestBuildHonoursOption(parse, build); TestAbsentQuery(parse); TestEmptyUri(parse); TestPresentQuery(parse);