diff --git a/CMakeLists.txt b/CMakeLists.txt index 34a1a65fc..b9609b1d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -490,6 +490,9 @@ add_executable(save_data_memory_tests EXCLUDE_FROM_ALL target_link_libraries(save_data_memory_tests common fmt::fmt) target_include_directories(save_data_memory_tests PRIVATE ${inc_headers}) +add_kyty_full_emulator_test(http_uri_parse_tests + "${KYTY_TESTS_DIR}/HttpUriParseTests.cpp") + add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp" "${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp" @@ -578,6 +581,7 @@ if(BUILD_TESTING) add_test(NAME ngs2_sampler COMMAND $) add_test(NAME pad_haptics COMMAND $) add_test(NAME save_data_memory COMMAND $) + add_test(NAME http_uri_parse COMMAND $) add_test(NAME shader_recompiler_compute COMMAND $) add_test(NAME shader_recompiler_alignbyte COMMAND $ --alignbyte-only) @@ -646,6 +650,7 @@ if(BUILD_TESTING) ngs2_sampler_tests pad_haptics_tests save_data_memory_tests + http_uri_parse_tests ) endif() diff --git a/src/libs/libNet.cpp b/src/libs/libNet.cpp index e2cd98a9e..35488c2b4 100644 --- a/src/libs/libNet.cpp +++ b/src/libs/libNet.cpp @@ -291,7 +291,7 @@ static char* CopyUriPart(char*& dst, const UriPart& part) { } static int ParseEmptyUri(SceHttpUriElement* out, void* pool, size_t* require, size_t prepare) { - constexpr size_t needed = 3; + constexpr size_t needed = 4; if (require != nullptr) { *require = needed; @@ -310,10 +310,12 @@ static int ParseEmptyUri(SceHttpUriElement* out, void* pool, size_t* require, si auto* dst = static_cast(pool); out->scheme = dst++; out->hostname = dst++; - out->path = dst; + out->path = dst++; + out->query = dst; out->scheme[0] = '\0'; out->hostname[0] = '\0'; out->path[0] = '\0'; + out->query[0] = '\0'; } return 0; @@ -474,6 +476,9 @@ static int KYTY_SYSV_ABI HttpUriParse(SceHttpUriElement* out, const char* src_ur needed += part.len + 1; } } + if (query.begin == nullptr) { + needed += 1; + } if (require != nullptr) { *require = needed; @@ -496,7 +501,12 @@ static int KYTY_SYSV_ABI HttpUriParse(SceHttpUriElement* out, const char* src_ur out->password = CopyUriPart(dst, password); out->hostname = CopyUriPart(dst, hostname); out->path = CopyUriPart(dst, path); - out->query = CopyUriPart(dst, query); + if (query.begin == nullptr) { + out->query = dst++; + out->query[0] = '\0'; + } else { + out->query = CopyUriPart(dst, query); + } out->fragment = CopyUriPart(dst, fragment); } diff --git a/tests/HttpUriParseTests.cpp b/tests/HttpUriParseTests.cpp new file mode 100644 index 000000000..ac537d631 --- /dev/null +++ b/tests/HttpUriParseTests.cpp @@ -0,0 +1,131 @@ +#include "common/abi.h" +#include "loader/symbolDatabase.h" + +#include +#include +#include +#include +#include + +namespace Libs::LibHttp { +void InitNet_1_Http(Loader::SymbolDatabase *symbols); +} + +namespace { + +struct SceHttpUriElement { + int opaque = 0; + char *scheme = nullptr; + char *username = nullptr; + char *password = nullptr; + char *hostname = nullptr; + char *path = nullptr; + char *query = nullptr; + char *fragment = nullptr; + uint16_t port = 0; + uint8_t reserved[10]{}; +}; + +int failures = 0; + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + std::fprintf(stderr, "HttpUriParseTests:%d: %s\n", __LINE__, \ + #condition); \ + failures++; \ + } \ + } while (false) + +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); + CHECK(record != nullptr); + return record != nullptr ? reinterpret_cast(record->vaddr) + : nullptr; +} + +template +bool PointsIntoPool(const char *ptr, const std::array &pool, + size_t used) { + if (ptr == nullptr) { + return false; + } + const auto address = reinterpret_cast(ptr); + const auto begin = reinterpret_cast(pool.data()); + return address >= begin && address < begin + used; +} + +void TestAbsentQuery(HttpUriParse parse) { + constexpr char url[] = "http://example.com/path"; + size_t required = 0; + CHECK(parse(nullptr, url, nullptr, &required, 0) == 0); + + const size_t expected_required = + sizeof("http") + sizeof("example.com") + sizeof("/path") + 1; + CHECK(required == expected_required); + + std::array pool{}; + SceHttpUriElement out{}; + size_t parsed_required = 0; + CHECK(parse(&out, url, pool.data(), &parsed_required, required) == 0); + CHECK(parsed_required == required); + CHECK(out.query != nullptr); + CHECK(PointsIntoPool(out.query, pool, required)); + if (out.query != nullptr) { + CHECK(out.query[0] == '\0'); + } +} + +void TestEmptyUri(HttpUriParse parse) { + constexpr char url[] = ""; + size_t required = 0; + CHECK(parse(nullptr, url, nullptr, &required, 0) == 0); + CHECK(required == 4); + + std::array pool{}; + SceHttpUriElement out{}; + size_t parsed_required = 0; + CHECK(parse(&out, url, pool.data(), &parsed_required, required) == 0); + CHECK(parsed_required == required); + CHECK(out.query != nullptr); + CHECK(PointsIntoPool(out.query, pool, required)); + if (out.query != nullptr) { + CHECK(out.query[0] == '\0'); + } +} + +void TestPresentQuery(HttpUriParse parse) { + constexpr char url[] = "http://example.com/path?foo=bar"; + size_t required = 0; + CHECK(parse(nullptr, url, nullptr, &required, 0) == 0); + + std::array pool{}; + SceHttpUriElement out{}; + size_t parsed_required = 0; + CHECK(parse(&out, url, pool.data(), &parsed_required, required) == 0); + CHECK(parsed_required == required); + CHECK(out.query != nullptr); + CHECK(PointsIntoPool(out.query, pool, required)); + if (out.query != nullptr) { + CHECK(std::strcmp(out.query, "?foo=bar") == 0); + } +} + +} // namespace + +int main() { + const auto parse = GetHttpUriParse(); + if (parse == nullptr) { + return 1; + } + TestAbsentQuery(parse); + TestEmptyUri(parse); + TestPresentQuery(parse); + return failures == 0 ? 0 : 1; +}