KytyPS5/tests/HttpUriParseTests.cpp

181 lines
6.0 KiB
C++

#include "common/abi.h"
#include "loader/symbolDatabase.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
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);
using HttpUriBuild = int(KYTY_SYSV_ABI *)(char *, size_t *, size_t,
const SceHttpUriElement *, uint32_t);
template <typename T>
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<T>(record->vaddr) : nullptr;
}
template <size_t N>
bool PointsIntoPool(const char *ptr, const std::array<char, N> &pool,
size_t used) {
if (ptr == nullptr) {
return false;
}
const auto address = reinterpret_cast<uintptr_t>(ptr);
const auto begin = reinterpret_cast<uintptr_t>(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<char, 64> 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<char, 4> 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<char, 64> 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);
}
}
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<char, 256> 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<char, 256> 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() {
Loader::SymbolDatabase symbols;
Libs::LibHttp::InitNet_1_Http(&symbols);
const auto parse = GetHttpFunction<HttpUriParse>(symbols, "IWalAn-guFs");
const auto build = GetHttpFunction<HttpUriBuild>(symbols, "5LZA+KPISVA");
if (parse == nullptr || build == nullptr) {
return 1;
}
TestBuildHonoursOption(parse, build);
TestAbsentQuery(parse);
TestEmptyUri(parse);
TestPresentQuery(parse);
return failures == 0 ? 0 : 1;
}