mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-06-10 03:35:00 -06:00
2103 lines
77 KiB
C++
2103 lines
77 KiB
C++
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <fstream>
|
|
|
|
#include "common/types.h"
|
|
#include "core/libraries/kernel/orbis_error.h"
|
|
#include "core/libraries/network/http.h"
|
|
#include "core/libraries/network/http_error.h"
|
|
#include "tests/stubs/kernel_stub.h"
|
|
|
|
#ifndef ORBIS_OK
|
|
#define ORBIS_OK 0
|
|
#endif
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpInit(int, int, u64);
|
|
int PS4_SYSV_ABI sceHttpTerm(int);
|
|
int PS4_SYSV_ABI sceHttpCreateTemplate(int, const char*, int, int);
|
|
int PS4_SYSV_ABI sceHttpDeleteTemplate(int);
|
|
int PS4_SYSV_ABI sceHttpCreateConnection(int, const char*, const char*, u16, int);
|
|
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL(int, const char*, bool);
|
|
int PS4_SYSV_ABI sceHttpDeleteConnection(int);
|
|
int PS4_SYSV_ABI sceHttpCreateRequest(int, int, const char*, u64);
|
|
int PS4_SYSV_ABI sceHttpCreateRequestWithURL(int, s32, const char*, u64);
|
|
int PS4_SYSV_ABI sceHttpDeleteRequest(int);
|
|
int PS4_SYSV_ABI sceHttpSendRequest(int, const void*, u64);
|
|
int PS4_SYSV_ABI sceHttpGetLastErrno(int, int*);
|
|
int PS4_SYSV_ABI sceHttpGetStatusCode(int, int*);
|
|
int PS4_SYSV_ABI sceHttpReadData(s32, void*, u64);
|
|
int PS4_SYSV_ABI sceHttpAbortRequest(int);
|
|
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int, char**, u64*);
|
|
} // namespace Libraries::Http
|
|
|
|
using namespace Libraries::Http;
|
|
|
|
namespace {
|
|
|
|
// State machine + no-internet path.. SendRequest records ENODNS on the request and GetLastErrno
|
|
// surfaces it.
|
|
|
|
// Drains every active context. Run before each test so previous test failures
|
|
// don't poison the static state.
|
|
static void DrainState() {
|
|
for (int i = 0; i < 1024; ++i) {
|
|
if (sceHttpTerm(i) != ORBIS_OK) {
|
|
// Either not active or library already torn down
|
|
}
|
|
}
|
|
}
|
|
|
|
class HttpLifecycle : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
DrainState();
|
|
}
|
|
void TearDown() override {
|
|
DrainState();
|
|
}
|
|
};
|
|
|
|
// Init returns a strictly-positive context id.
|
|
TEST_F(HttpLifecycle, InitReturnsContextId) {
|
|
int ctx = sceHttpInit(1, 2, 1024 * 16);
|
|
EXPECT_GT(ctx, 0);
|
|
EXPECT_EQ(sceHttpTerm(ctx), ORBIS_OK);
|
|
}
|
|
|
|
// Init with poolSize=0 returns EINVAL.
|
|
TEST_F(HttpLifecycle, InitZeroPoolReturnsEinval) {
|
|
EXPECT_EQ(sceHttpInit(0, 0, 0), static_cast<int>(ORBIS_KERNEL_ERROR_EINVAL));
|
|
}
|
|
|
|
// Term before Init returns BEFORE_INIT.
|
|
TEST_F(HttpLifecycle, TermBeforeInitFails) {
|
|
EXPECT_EQ(sceHttpTerm(1), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
// Two Inits, two Terms both context IDs are different, both terminate.
|
|
TEST_F(HttpLifecycle, MultipleContexts) {
|
|
int a = sceHttpInit(0, 0, 4096);
|
|
int b = sceHttpInit(0, 0, 4096);
|
|
EXPECT_GT(a, 0);
|
|
EXPECT_GT(b, 0);
|
|
EXPECT_NE(a, b);
|
|
EXPECT_EQ(sceHttpTerm(a), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpTerm(b), ORBIS_OK);
|
|
}
|
|
|
|
// Term of an already-terminated context returns INVALID_ID, not BEFORE_INIT.
|
|
TEST_F(HttpLifecycle, TermTwiceReturnsInvalidId) {
|
|
int a = sceHttpInit(0, 0, 4096);
|
|
int b = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpTerm(a), ORBIS_OK);
|
|
// The library is still inited via b; another Term of a should now fail
|
|
// with INVALID_ID.
|
|
EXPECT_EQ(sceHttpTerm(a), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
EXPECT_EQ(sceHttpTerm(b), ORBIS_OK);
|
|
}
|
|
|
|
// CreateTemplate before Init should return BEFORE_INIT.
|
|
TEST_F(HttpLifecycle, CreateTemplateBeforeInit) {
|
|
EXPECT_EQ(sceHttpCreateTemplate(1, "UA/1.0", 1, 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
// CreateTemplate with bogus context id should return INVALID_ID.
|
|
TEST_F(HttpLifecycle, CreateTemplateInvalidCtx) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpCreateTemplate(ctx + 99, "UA/1.0", 1, 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
EXPECT_EQ(sceHttpTerm(ctx), ORBIS_OK);
|
|
}
|
|
|
|
// Sequence : Init then CreateTemplate then CreateConnection then CreateRequest then SendRequest
|
|
// then GetLastErrno reports ENODNS then Delete chain succeeds.
|
|
TEST_F(HttpLifecycle, FullLifecycleSurfacesNoInternetError) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
ASSERT_GT(ctx, 0);
|
|
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA/1.0", 1, 0);
|
|
ASSERT_GT(tmpl, 0);
|
|
EXPECT_NE(tmpl, ctx); // different ID space
|
|
|
|
int conn = sceHttpCreateConnection(tmpl, "shadps4-test.invalid", "http", 80, 0);
|
|
ASSERT_GT(conn, 0);
|
|
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://shadps4-test.invalid/", 0);
|
|
ASSERT_GT(req, 0);
|
|
|
|
// Send dispatches a worker thread. Returns ORBIS_OK synchronously.
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), ORBIS_OK);
|
|
|
|
// A blocking getter (GetStatusCode) waits on the worker's cv. After it
|
|
// returns, the worker has finished and last_errno is populated. On the
|
|
// no-internet path the transport failure makes GetStatusCode return
|
|
// BEFORE_SEND (no status line was ever received).
|
|
int sc = 0;
|
|
EXPECT_EQ(sceHttpGetStatusCode(req, &sc), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_SEND));
|
|
|
|
// After the worker is done, GetLastErrno reports the resolver error.
|
|
int err = 0;
|
|
EXPECT_EQ(sceHttpGetLastErrno(req, &err), ORBIS_OK);
|
|
EXPECT_EQ(static_cast<unsigned>(err), 0x80436002u);
|
|
|
|
EXPECT_EQ(sceHttpDeleteRequest(req), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpDeleteConnection(conn), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpDeleteTemplate(tmpl), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpTerm(ctx), ORBIS_OK);
|
|
}
|
|
|
|
// SendRequest twice on the same reqId: first dispatches the worker, second
|
|
// is rejected with AFTER_SEND (state is already Sending or Sent).
|
|
TEST_F(HttpLifecycle, SendTwiceReturnsAfterSend) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), static_cast<int>(ORBIS_HTTP_ERROR_AFTER_SEND));
|
|
// Drain the worker so the next test starts clean.
|
|
int sc;
|
|
sceHttpGetStatusCode(req, &sc);
|
|
sceHttpDeleteRequest(req);
|
|
sceHttpDeleteConnection(conn);
|
|
sceHttpDeleteTemplate(tmpl);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// SendRequest on bogus id shoud return INVALID_ID.
|
|
TEST_F(HttpLifecycle, SendInvalidRequest) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpSendRequest(9999, nullptr, 0), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// GetLastErrno before send returns 0 (the default, no error recorded yet).
|
|
TEST_F(HttpLifecycle, GetLastErrnoBeforeSendIsZero) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
int err = 0xDEADBEEF;
|
|
EXPECT_EQ(sceHttpGetLastErrno(req, &err), ORBIS_OK);
|
|
EXPECT_EQ(err, 0);
|
|
sceHttpDeleteRequest(req);
|
|
sceHttpDeleteConnection(conn);
|
|
sceHttpDeleteTemplate(tmpl);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// GetLastErrno with null output pointer should return INVALID_VALUE.
|
|
TEST_F(HttpLifecycle, GetLastErrnoNullOut) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpGetLastErrno(req, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Term tears down all dependent objects: a request id from a terminated
|
|
// context is no longer valid.
|
|
TEST_F(HttpLifecycle, TermTearsDownDependents) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpTerm(ctx), ORBIS_OK);
|
|
|
|
// Operations after term must report BEFORE_INIT.
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
int err;
|
|
EXPECT_EQ(sceHttpGetLastErrno(req, &err), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
// Connection with null server name should return INVALID_VALUE.
|
|
TEST_F(HttpLifecycle, CreateConnectionNullServerName) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpCreateConnection(tmpl, nullptr, "http", 80, 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Two requests on the same connection get distinct IDs and isolated errnos.
|
|
TEST_F(HttpLifecycle, TwoRequestsAreIsolated) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int r1 = sceHttpCreateRequestWithURL(conn, 0, "http://x/a", 0);
|
|
int r2 = sceHttpCreateRequestWithURL(conn, 0, "http://x/b", 0);
|
|
EXPECT_NE(r1, r2);
|
|
|
|
EXPECT_EQ(sceHttpSendRequest(r1, nullptr, 0), ORBIS_OK);
|
|
// Wait for r1's worker via a blocking getter (GetStatusCode).
|
|
int sc;
|
|
sceHttpGetStatusCode(r1, &sc);
|
|
// r2 not yet sent
|
|
int err1 = 0, err2 = 0;
|
|
EXPECT_EQ(sceHttpGetLastErrno(r1, &err1), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpGetLastErrno(r2, &err2), ORBIS_OK);
|
|
EXPECT_EQ(static_cast<unsigned>(err1), 0x80436002u);
|
|
EXPECT_EQ(err2, 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, CreateConnectionNullServerNameTakesPriorityOverBadScheme) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpCreateConnection(tmpl, nullptr, "ftp", 80, 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, CreateConnectionUnknownScheme) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpCreateConnection(tmpl, "example.com", "ftp", 80, 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_UNKNOWN_SCHEME));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, CreateConnectionPortZeroAccepted) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_GT(sceHttpCreateConnection(tmpl, "example.com", "http", 0, 0), 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, CreateRequestRejectsOptions) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
EXPECT_EQ(sceHttpCreateRequestWithURL(conn, /*Options=*/3, "http://example.com/", 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_UNKNOWN_METHOD));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, CreateRequestRejectsConnect) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
EXPECT_EQ(sceHttpCreateRequestWithURL(conn, /*Connect=*/7, "http://example.com/", 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_UNKNOWN_METHOD));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, CreateRequestRejectsOutOfRangeMethod) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
EXPECT_EQ(sceHttpCreateRequestWithURL(conn, 9, "http://example.com/", 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_UNKNOWN_METHOD));
|
|
EXPECT_EQ(sceHttpCreateRequestWithURL(conn, 99, "http://example.com/", 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_UNKNOWN_METHOD));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, CreateRequestAcceptsValidMethods) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
for (int m : {0, 1, 2, 4, 5, 6, 8}) {
|
|
int r = sceHttpCreateRequestWithURL(conn, m, "http://example.com/", 0);
|
|
EXPECT_GT(r, 0) << "method " << m << " unexpectedly rejected";
|
|
if (r > 0)
|
|
sceHttpDeleteRequest(r);
|
|
}
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpCreateRequestWithURL2(int, const char*, const char*, u64);
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Verifies the WithURL2 ordering
|
|
TEST_F(HttpLifecycle, WithURL2BeforeInitTakesPriorityOverNullMethod) {
|
|
// Library uninitialized (DrainState ran via SetUp).
|
|
EXPECT_EQ(sceHttpCreateRequestWithURL2(/*connId=*/1, nullptr, "http://x/", 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
// Initialized but bogus connId + NULL method should return INVALID_ID
|
|
TEST_F(HttpLifecycle, WithURL2InvalidIdTakesPriorityOverNullMethod) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpCreateRequestWithURL2(/*connId=*/9999, nullptr, "http://x/", 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Initialized + valid connId + NULL method should return INVALID_VALUE
|
|
TEST_F(HttpLifecycle, WithURL2NullMethodAfterValidConn) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
EXPECT_EQ(sceHttpCreateRequestWithURL2(conn, nullptr, "http://example.com/", 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpSetAutoRedirect(int id, int isEnable);
|
|
int PS4_SYSV_ABI sceHttpGetAutoRedirect(int id, int* isEnable);
|
|
int PS4_SYSV_ABI sceHttpSetConnectTimeOut(int id, u32 usec);
|
|
} // namespace Libraries::Http
|
|
|
|
namespace {
|
|
|
|
// Connection snapshots template settings at creation.
|
|
TEST_F(HttpLifecycle, ConnectionSnapshotsTemplateAutoRedirectAtCreation) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
|
|
// Flip the template's default (which is true) to false.
|
|
EXPECT_EQ(sceHttpSetAutoRedirect(tmpl, 0), ORBIS_OK);
|
|
|
|
// Connection created NOW should snapshot redirect=false.
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
ASSERT_GT(conn, 0);
|
|
|
|
// Flip the template AGAIN to true. The connection should NOT change.
|
|
EXPECT_EQ(sceHttpSetAutoRedirect(tmpl, 1), ORBIS_OK);
|
|
|
|
int got = 99;
|
|
EXPECT_EQ(sceHttpGetAutoRedirect(conn, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 0) << "connection should keep snapshot value, not follow template";
|
|
|
|
// And the template's current value is now 1.
|
|
EXPECT_EQ(sceHttpGetAutoRedirect(tmpl, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Request snapshots connection settings at creation.
|
|
TEST_F(HttpLifecycle, RequestSnapshotsConnectionSettingsAtCreation) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
ASSERT_GT(conn, 0);
|
|
|
|
// Default auto_redirect on conn is true (inherited from tmpl default).
|
|
int got = -1;
|
|
EXPECT_EQ(sceHttpGetAutoRedirect(conn, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
|
|
// Flip the connection to false BEFORE creating the request.
|
|
EXPECT_EQ(sceHttpSetAutoRedirect(conn, 0), ORBIS_OK);
|
|
|
|
int req = sceHttpCreateRequestWithURL(conn, /*method=*/0, "http://example.com/", 0);
|
|
ASSERT_GT(req, 0);
|
|
|
|
// Now flip the connection back to true. Should NOT affect the request.
|
|
EXPECT_EQ(sceHttpSetAutoRedirect(conn, 1), ORBIS_OK);
|
|
|
|
got = 99;
|
|
EXPECT_EQ(sceHttpGetAutoRedirect(req, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 0) << "request should keep snapshot, not follow connection";
|
|
|
|
// Connection's current value is 1.
|
|
EXPECT_EQ(sceHttpGetAutoRedirect(conn, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Default chain: template default then connection default then request default
|
|
// should all be auto_redirect=true
|
|
TEST_F(HttpLifecycle, AutoRedirectDefaultsPropagateThroughCreation) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://example.com/", 0);
|
|
|
|
int got = -1;
|
|
EXPECT_EQ(sceHttpGetAutoRedirect(tmpl, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
EXPECT_EQ(sceHttpGetAutoRedirect(conn, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
EXPECT_EQ(sceHttpGetAutoRedirect(req, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Timeouts also snapshot correctly
|
|
TEST_F(HttpLifecycle, ConnectTimeoutSnapshotsTemplateValue) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
|
|
EXPECT_EQ(sceHttpSetConnectTimeOut(tmpl, 7'000'000), ORBIS_OK);
|
|
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
ASSERT_GT(conn, 0);
|
|
|
|
EXPECT_EQ(sceHttpSetConnectTimeOut(tmpl, 99'000'000), ORBIS_OK);
|
|
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Async state machine sanity tests
|
|
namespace {
|
|
|
|
// After SendRequest, a blocking ReadData waits for the worker, then returns 0
|
|
TEST_F(HttpLifecycle, ReadDataReturnsZeroAfterTransportFailure) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), ORBIS_OK);
|
|
char buf[16];
|
|
EXPECT_EQ(sceHttpReadData(req, buf, sizeof(buf)), 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// ReadData on an unsent request should return BEFORE_SEND (Created state).
|
|
TEST_F(HttpLifecycle, ReadDataBeforeSendReturnsBeforeSend) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
char buf[16];
|
|
EXPECT_EQ(sceHttpReadData(req, buf, sizeof(buf)),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_SEND));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// GetStatusCode on an aborted request should return ABORTED.
|
|
TEST_F(HttpLifecycle, GetStatusCodeOnAbortedRequest) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpAbortRequest(req), ORBIS_OK);
|
|
int sc;
|
|
EXPECT_EQ(sceHttpGetStatusCode(req, &sc), static_cast<int>(ORBIS_HTTP_ERROR_ABORTED));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// AbortRequest on unsent request: state goes to Aborted, subsequent Send
|
|
// returns ABORTED.
|
|
TEST_F(HttpLifecycle, AbortBeforeSend) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpAbortRequest(req), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), static_cast<int>(ORBIS_HTTP_ERROR_ABORTED));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, GetAllResponseHeadersEmptyOnTransportFailure) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), ORBIS_OK);
|
|
char* hdr = reinterpret_cast<char*>(0xdeadbeef);
|
|
u64 hdr_size = 999;
|
|
EXPECT_EQ(sceHttpGetAllResponseHeaders(req, &hdr, &hdr_size), ORBIS_OK);
|
|
EXPECT_EQ(hdr, nullptr);
|
|
EXPECT_EQ(hdr_size, 0u);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// GetAllResponseHeaders before Send should return BEFORE_SEND.
|
|
TEST_F(HttpLifecycle, GetAllResponseHeadersBeforeSend) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
char* hdr = nullptr;
|
|
u64 hdr_size = 0;
|
|
EXPECT_EQ(sceHttpGetAllResponseHeaders(req, &hdr, &hdr_size),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_SEND));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Null output pointer should return INVALID_VALUE (init OK, no reqId lookup).
|
|
TEST_F(HttpLifecycle, GetAllResponseHeadersNullOut) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpGetAllResponseHeaders(0, nullptr, nullptr),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, AbortAfterSendIsIdempotent) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), ORBIS_OK);
|
|
int sc;
|
|
EXPECT_EQ(sceHttpGetStatusCode(req, &sc), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_SEND));
|
|
EXPECT_EQ(sceHttpAbortRequest(req), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpGetStatusCode(req, &sc), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_SEND));
|
|
int err = 0;
|
|
EXPECT_EQ(sceHttpGetLastErrno(req, &err), ORBIS_OK);
|
|
EXPECT_EQ(static_cast<unsigned>(err), 0x80436002u);
|
|
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Aborting twice on the same request is a no-op the second time.
|
|
TEST_F(HttpLifecycle, AbortTwiceIsIdempotent) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
|
|
EXPECT_EQ(sceHttpAbortRequest(req), ORBIS_OK);
|
|
// Second call: idempotent OK.
|
|
EXPECT_EQ(sceHttpAbortRequest(req), ORBIS_OK);
|
|
|
|
int sc;
|
|
EXPECT_EQ(sceHttpGetStatusCode(req, &sc), static_cast<int>(ORBIS_HTTP_ERROR_ABORTED));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpSetNonblock(int, int);
|
|
int PS4_SYSV_ABI sceHttpGetNonblock(int, int*);
|
|
int PS4_SYSV_ABI sceHttpTrySetNonblock(int, int);
|
|
int PS4_SYSV_ABI sceHttpTryGetNonblock(int, int*);
|
|
} // namespace Libraries::Http
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, GetNonblockDefaultIsBlocking) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int got = -1;
|
|
EXPECT_EQ(sceHttpGetNonblock(tmpl, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Set + Get round-trip.
|
|
TEST_F(HttpLifecycle, SetGetNonblockRoundTrip) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int got = -1;
|
|
EXPECT_EQ(sceHttpSetNonblock(tmpl, 1), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpGetNonblock(tmpl, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
EXPECT_EQ(sceHttpSetNonblock(tmpl, 0), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpGetNonblock(tmpl, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Try variants delegate to the regular ones.
|
|
TEST_F(HttpLifecycle, TryNonblockBehavesAsRegular) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int got = -1;
|
|
EXPECT_EQ(sceHttpTrySetNonblock(tmpl, 1), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpTryGetNonblock(tmpl, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Nonblock accepts template/connection/request IDs.
|
|
TEST_F(HttpLifecycle, SetNonblockAtRequestLevel) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpSetNonblock(req, 1), ORBIS_OK);
|
|
int got = -1;
|
|
EXPECT_EQ(sceHttpGetNonblock(req, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, NonblockSnapshotsAtCreation) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetNonblock(tmpl, 1), ORBIS_OK);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
// Conn should have snapshot value = 1.
|
|
int got = -1;
|
|
EXPECT_EQ(sceHttpGetNonblock(conn, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
// Flip template to 0; conn keeps its snapshot of 1.
|
|
EXPECT_EQ(sceHttpSetNonblock(tmpl, 0), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpGetNonblock(conn, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
EXPECT_EQ(sceHttpGetNonblock(tmpl, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, GetNonblockInvalidId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int got = -1;
|
|
EXPECT_EQ(sceHttpGetNonblock(99999, &got), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// NULL output pointer on Get should return INVALID_VALUE.
|
|
TEST_F(HttpLifecycle, GetNonblockNullOut) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpGetNonblock(tmpl, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpGetResponseContentLength(int, int*, u64*);
|
|
}
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, NonblockReadDataAfterCompletionReturnsZero) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), ORBIS_OK);
|
|
// Drain via blocking GetStatusCode (default blocking mode).
|
|
int sc;
|
|
sceHttpGetStatusCode(req, &sc);
|
|
// Flip to nonblock - worker is done, nonblock check no longer fires.
|
|
sceHttpSetNonblock(req, 1);
|
|
char buf[16];
|
|
EXPECT_EQ(sceHttpReadData(req, buf, sizeof(buf)), 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, NonblockGetStatusCodeAfterCompletionWorks) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), ORBIS_OK);
|
|
// Drain via blocking GetStatusCode first (state Sent).
|
|
int sc;
|
|
sceHttpGetStatusCode(req, &sc);
|
|
// Flip to nonblock now; worker is done.
|
|
sceHttpSetNonblock(req, 1);
|
|
int r = sceHttpGetStatusCode(req, &sc);
|
|
// For a transport-failed request, GetStatusCode returns BEFORE_SEND
|
|
// (because last_errno != 0 and status_code == 0).
|
|
EXPECT_EQ(r, static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_SEND));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, NonblockGetResponseContentLengthAfterCompletion) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), ORBIS_OK);
|
|
// Drain worker via blocking GetStatusCode (request is still in default
|
|
// blocking mode).
|
|
int sc;
|
|
sceHttpGetStatusCode(req, &sc);
|
|
// Now flip to nonblock. The worker is done; subsequent getters should
|
|
// succeed (nonblock only affects the Sending state).
|
|
sceHttpSetNonblock(req, 1);
|
|
int result = -999;
|
|
u64 content_length = 999;
|
|
int r = sceHttpGetResponseContentLength(req, &result, &content_length);
|
|
EXPECT_EQ(r, ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpCreateRequest2(int, const char*, const char*, u64);
|
|
int PS4_SYSV_ABI sceHttpCreateRequestWithURL2(int, const char*, const char*, u64);
|
|
} // namespace Libraries::Http
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, CreateRequest2DoesNotDeadlock) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
int req = sceHttpCreateRequest2(conn, "GET", "/path", 0);
|
|
EXPECT_GT(req, 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, CreateRequestWithURL2DoesNotDeadlock) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL2(conn, "POST", "http://example.com/post", 0);
|
|
EXPECT_GT(req, 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// CreateRequest2 with non-standard method routes via CUSTOM slot.
|
|
TEST_F(HttpLifecycle, CreateRequest2CustomMethodWorks) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
int req = sceHttpCreateRequest2(conn, "PATCH", "/resource", 0);
|
|
EXPECT_GT(req, 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// CreateRequest2 NULL method should return INVALID_VALUE
|
|
TEST_F(HttpLifecycle, CreateRequest2NullMethod) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
int req = sceHttpCreateRequest2(conn, nullptr, "/path", 0);
|
|
EXPECT_EQ(req, static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// CreateRequest2 NULL path should return INVALID_VALUE.
|
|
TEST_F(HttpLifecycle, CreateRequest2NullPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "example.com", "http", 80, 0);
|
|
int req = sceHttpCreateRequest2(conn, "GET", nullptr, 0);
|
|
EXPECT_EQ(req, static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Epoll lifecycle tests
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpCreateEpoll(int, OrbisHttpEpollHandle*);
|
|
int PS4_SYSV_ABI sceHttpDestroyEpoll(int, OrbisHttpEpollHandle);
|
|
int PS4_SYSV_ABI sceHttpSetEpoll(int, OrbisHttpEpollHandle, void*);
|
|
int PS4_SYSV_ABI sceHttpGetEpoll(int, OrbisHttpEpollHandle*, void**);
|
|
int PS4_SYSV_ABI sceHttpUnsetEpoll(int);
|
|
int PS4_SYSV_ABI sceHttpWaitRequest(OrbisHttpEpollHandle, OrbisHttpNBEvent*, int, int);
|
|
int PS4_SYSV_ABI sceHttpAbortWaitRequest(OrbisHttpEpollHandle);
|
|
} // namespace Libraries::Http
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, CreateAndDestroyEpoll) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
EXPECT_EQ(sceHttpCreateEpoll(ctx, &eh), ORBIS_OK);
|
|
EXPECT_NE(eh, nullptr);
|
|
EXPECT_EQ(sceHttpDestroyEpoll(ctx, eh), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, CreateEpollNullOut) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpCreateEpoll(ctx, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, CreateEpollInvalidCtxId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
EXPECT_EQ(sceHttpCreateEpoll(99999, &eh), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, DestroyEpollNullHandle) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpDestroyEpoll(ctx, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, DestroyEpollInvalidHandle) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpEpollHandle bogus = reinterpret_cast<OrbisHttpEpollHandle>(uintptr_t{99999});
|
|
EXPECT_EQ(sceHttpDestroyEpoll(ctx, bogus), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetGetEpollRoundTripOnTemplate) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
sceHttpCreateEpoll(ctx, &eh);
|
|
int magic = 0;
|
|
void* user_arg = &magic;
|
|
EXPECT_EQ(sceHttpSetEpoll(tmpl, eh, user_arg), ORBIS_OK);
|
|
|
|
OrbisHttpEpollHandle got_eh = nullptr;
|
|
void* got_user_arg = nullptr;
|
|
EXPECT_EQ(sceHttpGetEpoll(tmpl, &got_eh, &got_user_arg), ORBIS_OK);
|
|
EXPECT_EQ(got_eh, eh);
|
|
EXPECT_EQ(got_user_arg, user_arg);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetEpollInvalidEpollId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
OrbisHttpEpollHandle bogus = reinterpret_cast<OrbisHttpEpollHandle>(uintptr_t{99999});
|
|
EXPECT_EQ(sceHttpSetEpoll(tmpl, bogus, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetEpollInvalidOwnerId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
sceHttpCreateEpoll(ctx, &eh);
|
|
EXPECT_EQ(sceHttpSetEpoll(99999, eh, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, EpollBindingSnapshotsFromTemplateToConnectionToRequest) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
sceHttpCreateEpoll(ctx, &eh);
|
|
int magic = 0;
|
|
EXPECT_EQ(sceHttpSetEpoll(tmpl, eh, &magic), ORBIS_OK);
|
|
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
OrbisHttpEpollHandle got_eh = nullptr;
|
|
void* got_user_arg = nullptr;
|
|
EXPECT_EQ(sceHttpGetEpoll(conn, &got_eh, &got_user_arg), ORBIS_OK);
|
|
EXPECT_EQ(got_eh, eh);
|
|
EXPECT_EQ(got_user_arg, &magic);
|
|
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpGetEpoll(req, &got_eh, &got_user_arg), ORBIS_OK);
|
|
EXPECT_EQ(got_eh, eh);
|
|
EXPECT_EQ(got_user_arg, &magic);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, UnsetEpollIsRequestOnly) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpUnsetEpoll(tmpl), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
EXPECT_EQ(sceHttpUnsetEpoll(conn), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
// Request id works.
|
|
EXPECT_EQ(sceHttpUnsetEpoll(req), ORBIS_OK);
|
|
// After unset, GetEpoll returns 0 / nullptr.
|
|
OrbisHttpEpollHandle got_eh = reinterpret_cast<OrbisHttpEpollHandle>(uintptr_t{0xdead});
|
|
void* got_user_arg = reinterpret_cast<void*>(uintptr_t{0xdead});
|
|
EXPECT_EQ(sceHttpGetEpoll(req, &got_eh, &got_user_arg), ORBIS_OK);
|
|
EXPECT_EQ(got_eh, nullptr);
|
|
EXPECT_EQ(got_user_arg, nullptr);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, WaitRequestInvalidArgs) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
sceHttpCreateEpoll(ctx, &eh);
|
|
OrbisHttpNBEvent ev{};
|
|
// maxevents <= 0
|
|
EXPECT_EQ(sceHttpWaitRequest(eh, &ev, 0, 0), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
EXPECT_EQ(sceHttpWaitRequest(eh, nullptr, 1, 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
EXPECT_EQ(sceHttpWaitRequest(nullptr, &ev, 1, 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, WaitRequestPollEmptyReturnsZero) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
sceHttpCreateEpoll(ctx, &eh);
|
|
OrbisHttpNBEvent ev{};
|
|
EXPECT_EQ(sceHttpWaitRequest(eh, &ev, 1, 0), 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, WaitRequestDrainsWorkerCompletionEvent) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
sceHttpCreateEpoll(ctx, &eh);
|
|
int magic = 0;
|
|
sceHttpSetEpoll(tmpl, eh, &magic);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), ORBIS_OK);
|
|
// Drain via blocking getter so the worker is guaranteed to have run.
|
|
int sc;
|
|
sceHttpGetStatusCode(req, &sc);
|
|
// Now drain the epoll queue.
|
|
OrbisHttpNBEvent ev{};
|
|
int count = sceHttpWaitRequest(eh, &ev, 1, 0);
|
|
EXPECT_EQ(count, 1);
|
|
EXPECT_EQ(ev.id, req);
|
|
EXPECT_EQ(ev.userArg, &magic);
|
|
// No-internet path: failure bits (SOCK_ERR | HUP) are present.
|
|
EXPECT_NE(ev.events & 0x00020010u, 0u); // RESOLVER_ERR (0x20000) or HUP (0x10)
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, AbortWaitRequestNullHandle) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpAbortWaitRequest(nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, AbortWaitRequestInvalidHandle) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpEpollHandle bogus = reinterpret_cast<OrbisHttpEpollHandle>(uintptr_t{99999});
|
|
EXPECT_EQ(sceHttpAbortWaitRequest(bogus), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, TermClearsEpolls) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
sceHttpCreateEpoll(ctx, &eh);
|
|
sceHttpTerm(ctx);
|
|
// After Term, using the epoll handle returns BEFORE_INIT.
|
|
OrbisHttpNBEvent ev{};
|
|
EXPECT_EQ(sceHttpWaitRequest(eh, &ev, 1, 0), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, AllEpollCallsBeforeInitReturnBeforeInit) {
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
OrbisHttpEpollHandle stub = reinterpret_cast<OrbisHttpEpollHandle>(uintptr_t{1});
|
|
OrbisHttpNBEvent ev{};
|
|
void* ua = nullptr;
|
|
EXPECT_EQ(sceHttpCreateEpoll(0, &eh), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
EXPECT_EQ(sceHttpDestroyEpoll(0, stub), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
EXPECT_EQ(sceHttpSetEpoll(0, stub, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
EXPECT_EQ(sceHttpGetEpoll(0, &eh, &ua), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
EXPECT_EQ(sceHttpUnsetEpoll(0), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
EXPECT_EQ(sceHttpWaitRequest(stub, &ev, 1, 0), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
EXPECT_EQ(sceHttpAbortWaitRequest(stub), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, CreateEpollBadCtxAndNullOutReturnsInvalidId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpCreateEpoll(99999, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// DestroyEpoll: same firmware order.
|
|
TEST_F(HttpLifecycle, DestroyEpollBadCtxAndNullEhReturnsInvalidId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpDestroyEpoll(99999, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, WaitRequestAfterAbortReturnsAborted) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
sceHttpCreateEpoll(ctx, &eh);
|
|
EXPECT_EQ(sceHttpAbortWaitRequest(eh), ORBIS_OK);
|
|
OrbisHttpNBEvent ev{};
|
|
int r = sceHttpWaitRequest(eh, &ev, 1, 0);
|
|
EXPECT_EQ(r, static_cast<int>(ORBIS_HTTP_ERROR_ABORTED));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, WaitRequestAbortedTakesPriorityOverQueuedEvents) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
OrbisHttpEpollHandle eh = nullptr;
|
|
sceHttpCreateEpoll(ctx, &eh);
|
|
sceHttpSetEpoll(tmpl, eh, nullptr);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpSendRequest(req, nullptr, 0), ORBIS_OK);
|
|
int sc;
|
|
sceHttpGetStatusCode(req, &sc); // drain worker (pushes a failure event)
|
|
// Abort first, then call WaitRequest.
|
|
sceHttpAbortWaitRequest(eh);
|
|
OrbisHttpNBEvent ev{};
|
|
int r = sceHttpWaitRequest(eh, &ev, 1, 0);
|
|
EXPECT_EQ(r, static_cast<int>(ORBIS_HTTP_ERROR_ABORTED));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// sceHttpAddRequestHeader tests
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpAddRequestHeader(int, const char*, const char*, s32);
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Happy path: add a header on a template id, no errors.
|
|
TEST_F(HttpLifecycle, AddRequestHeaderTemplateHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpAddRequestHeader(tmpl, "Content-Type", "application/json", 1), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Headers can be added on connection ids too.
|
|
TEST_F(HttpLifecycle, AddRequestHeaderConnectionIdAccepted) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
EXPECT_EQ(sceHttpAddRequestHeader(conn, "X-Custom", "foo", 1), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// And on request ids.
|
|
TEST_F(HttpLifecycle, AddRequestHeaderRequestIdAccepted) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
EXPECT_EQ(sceHttpAddRequestHeader(req, "X-Custom", "bar", 1), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// BEFORE_INIT when library not inited.
|
|
TEST_F(HttpLifecycle, AddRequestHeaderBeforeInit) {
|
|
EXPECT_EQ(sceHttpAddRequestHeader(1, "Foo", "bar", 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
// INVALID_VALUE for mode out of range.
|
|
TEST_F(HttpLifecycle, AddRequestHeaderInvalidMode) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpAddRequestHeader(tmpl, "Foo", "bar", 2),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
EXPECT_EQ(sceHttpAddRequestHeader(tmpl, "Foo", "bar", -1),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// INVALID_VALUE for null name.
|
|
TEST_F(HttpLifecycle, AddRequestHeaderNullName) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpAddRequestHeader(tmpl, nullptr, "bar", 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// INVALID_ID for non-existent id.
|
|
TEST_F(HttpLifecycle, AddRequestHeaderInvalidId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpAddRequestHeader(99999, "Foo", "bar", 0),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, AddRequestHeaderModeCheckedBeforeId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpAddRequestHeader(99999, "Foo", "bar", 2),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, AddRequestHeaderNullValue) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpAddRequestHeader(tmpl, "X-Empty", nullptr, 1),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// sceHttpRemoveRequestHeader tests
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpRemoveRequestHeader(int, const char*);
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Add then remove: succeeds.
|
|
TEST_F(HttpLifecycle, RemoveRequestHeaderHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpAddRequestHeader(tmpl, "X-Foo", "bar", 1), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(tmpl, "X-Foo"), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(tmpl, "X-Foo"),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_NOT_FOUND));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Remove when the header was never added should return NOT_FOUND.
|
|
TEST_F(HttpLifecycle, RemoveRequestHeaderNotPresent) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(tmpl, "Nonexistent"),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_NOT_FOUND));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, RemoveRequestHeaderRemovesAllDuplicates) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
sceHttpAddRequestHeader(tmpl, "X-Dup", "v1", 1);
|
|
sceHttpAddRequestHeader(tmpl, "X-Dup", "v2", 1);
|
|
sceHttpAddRequestHeader(tmpl, "X-Dup", "v3", 1);
|
|
sceHttpAddRequestHeader(tmpl, "X-Other", "keep", 1);
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(tmpl, "X-Dup"), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(tmpl, "X-Dup"),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_NOT_FOUND));
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(tmpl, "X-Other"), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Case-insensitive match per HTTP semantics.
|
|
TEST_F(HttpLifecycle, RemoveRequestHeaderCaseInsensitive) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
sceHttpAddRequestHeader(tmpl, "Content-Type", "application/json", 1);
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(tmpl, "content-type"), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// BEFORE_INIT when library not inited.
|
|
TEST_F(HttpLifecycle, RemoveRequestHeaderBeforeInit) {
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(1, "X-Foo"),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
// Null nameshould return NOT_FOUND
|
|
TEST_F(HttpLifecycle, RemoveRequestHeaderNullNameReturnsNotFound) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(tmpl, nullptr),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_NOT_FOUND));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, RemoveRequestHeaderInvalidId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(99999, "X-Foo"),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Works on connection ids too.
|
|
TEST_F(HttpLifecycle, RemoveRequestHeaderConnectionIdAccepted) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
sceHttpAddRequestHeader(conn, "X-Custom", "foo", 1);
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(conn, "X-Custom"), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// And request ids.
|
|
TEST_F(HttpLifecycle, RemoveRequestHeaderRequestIdAccepted) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int req = sceHttpCreateRequestWithURL(conn, 0, "http://x/", 0);
|
|
sceHttpAddRequestHeader(req, "X-Custom", "bar", 1);
|
|
EXPECT_EQ(sceHttpRemoveRequestHeader(req, "X-Custom"), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpSetAcceptEncodingGZIPEnabled(int, int);
|
|
int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled(int, int*);
|
|
int PS4_SYSV_ABI sceHttpSetDefaultAcceptEncodingGZIPEnabled(int, int);
|
|
int PS4_SYSV_ABI sceHttpSetResolveTimeOut(int, u32);
|
|
int PS4_SYSV_ABI sceHttpSetResolveRetry(int, int);
|
|
int PS4_SYSV_ABI sceHttpSetRecvBlockSize(int, u32);
|
|
int PS4_SYSV_ABI sceHttpSetResponseHeaderMaxSize(int, u64);
|
|
} // namespace Libraries::Http
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, AcceptEncodingGZIPDefaultIsTrue) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int got = -1;
|
|
EXPECT_EQ(sceHttpGetAcceptEncodingGZIPEnabled(tmpl, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, AcceptEncodingGZIPRoundTrip) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetAcceptEncodingGZIPEnabled(tmpl, 0), ORBIS_OK);
|
|
int got = -1;
|
|
EXPECT_EQ(sceHttpGetAcceptEncodingGZIPEnabled(tmpl, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 0);
|
|
EXPECT_EQ(sceHttpSetAcceptEncodingGZIPEnabled(tmpl, 1), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpGetAcceptEncodingGZIPEnabled(tmpl, &got), ORBIS_OK);
|
|
EXPECT_EQ(got, 1);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, AcceptEncodingGZIPGetNullPtr) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpGetAcceptEncodingGZIPEnabled(tmpl, nullptr),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, AcceptEncodingGZIPSnapshotsToConnection) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
sceHttpSetAcceptEncodingGZIPEnabled(tmpl, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
int got = -1;
|
|
sceHttpGetAcceptEncodingGZIPEnabled(conn, &got);
|
|
EXPECT_EQ(got, 0); // inherits from template
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// --- SetDefault: library global affects future templates ---
|
|
|
|
TEST_F(HttpLifecycle, SetDefaultAcceptEncodingGZIPAffectsNewTemplates) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpSetDefaultAcceptEncodingGZIPEnabled(ctx, 0), ORBIS_OK);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int got = -1;
|
|
sceHttpGetAcceptEncodingGZIPEnabled(tmpl, &got);
|
|
EXPECT_EQ(got, 0); // picked up the new default
|
|
// Flip back so following tests aren't affected by global state.
|
|
sceHttpSetDefaultAcceptEncodingGZIPEnabled(ctx, 1);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetDefaultAcceptEncodingGZIPBeforeInit) {
|
|
EXPECT_EQ(sceHttpSetDefaultAcceptEncodingGZIPEnabled(1, 1),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
// --- ResolveTimeOut validation ---
|
|
|
|
TEST_F(HttpLifecycle, SetResolveTimeOutHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
// SDK >= 1.70 requires usec > 999999 per firmware line 16020.
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 5000000u), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetResolveTimeOutMinAccepted) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
// 1000000 is one above the firmware threshold (must be > 999999).
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 1000000u), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetResolveTimeOutSmallRejected) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
// 999999 fails the strict (usec > 999999) check on SDK >= 1.70.
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 999999u),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 500000u),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetResolveTimeOutBeforeInit) {
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(1, 100), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetResolveTimeOutOutOfRangeBeforeBadId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
// usec=100 fails the > 999999 check on SDK >= 1.70 before id is looked up.
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(99999, 100u),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetResolveTimeOut_PreSDK170_AcceptsSmallUsec) {
|
|
Libraries::Kernel::TestSetSdkVersion(0x1600000);
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 100u), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 0u), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
Libraries::Kernel::TestResetSdkVersion();
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetResolveTimeOut_AtSDK170_AppliesStrictCheck) {
|
|
Libraries::Kernel::TestSetSdkVersion(0x1700000);
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 999999u),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 1000000u), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
Libraries::Kernel::TestResetSdkVersion();
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetResolveTimeOut_PreSDK170_OutOfRangeStillAccepted) {
|
|
Libraries::Kernel::TestSetSdkVersion(0x1600000);
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 999999u), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 500000u), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
Libraries::Kernel::TestResetSdkVersion();
|
|
}
|
|
|
|
// --- ResolveRetry ---
|
|
|
|
TEST_F(HttpLifecycle, SetResolveRetryHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetResolveRetry(tmpl, 0), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpSetResolveRetry(tmpl, 3), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpSetResolveRetry(tmpl, 100), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetResolveRetryNegativeRejected) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetResolveRetry(tmpl, -1), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Firmware ordering: retry < 0 checked BEFORE id lookup.
|
|
TEST_F(HttpLifecycle, SetResolveRetryNegativeBeforeBadId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpSetResolveRetry(99999, -5), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// --- RecvBlockSize ---
|
|
|
|
TEST_F(HttpLifecycle, SetRecvBlockSizeHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetRecvBlockSize(tmpl, 16384), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetRecvBlockSizeInvalidId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpSetRecvBlockSize(99999, 4096), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// --- ResponseHeaderMaxSize ---
|
|
|
|
TEST_F(HttpLifecycle, SetResponseHeaderMaxSizeHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetResponseHeaderMaxSize(tmpl, 8192), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetResponseHeaderMaxSizeInvalidId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpSetResponseHeaderMaxSize(99999, 8192),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_F(HttpLifecycle, SetResolveTimeOutOldSdkSkipsValidation) {
|
|
Libraries::Kernel::TestSetSdkVersion(0x1000000); // pre-1.70
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
// Both small and large usec accepted on old SDK.
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 500u), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 999999u), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 5000000u), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
Libraries::Kernel::TestResetSdkVersion();
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetResolveTimeOutNewSdkEnforces) {
|
|
Libraries::Kernel::TestSetSdkVersion(0x1700000); // exactly 1.70
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 999999u),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 500000u),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 1000000u), ORBIS_OK);
|
|
EXPECT_EQ(sceHttpSetResolveTimeOut(tmpl, 35000000u), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
Libraries::Kernel::TestResetSdkVersion();
|
|
}
|
|
|
|
// sceHttpSetProxy tests
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpSetProxy(int, int, int, const char*, u16);
|
|
}
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, SetProxyHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
// mode 1 = manual proxy, host:port specified
|
|
EXPECT_EQ(sceHttpSetProxy(tmpl, 1, 0, "proxy.example.com", 8080), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetProxyOnConnection) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
EXPECT_EQ(sceHttpSetProxy(conn, 1, 0, "proxy.example.com", 3128), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetProxyBeforeInit) {
|
|
EXPECT_EQ(sceHttpSetProxy(1, 0, 0, "host", 80), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetProxyNullHost) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
EXPECT_EQ(sceHttpSetProxy(tmpl, 1, 0, nullptr, 8080),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetProxyNullHostBeforeBadId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpSetProxy(99999, 1, 0, nullptr, 8080),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, SetProxyInvalidId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpSetProxy(99999, 1, 0, "host", 80),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
// Connection inherits proxy from template at creation.
|
|
TEST_F(HttpLifecycle, SetProxyInheritedByConnection) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
int tmpl = sceHttpCreateTemplate(ctx, "UA", 1, 0);
|
|
sceHttpSetProxy(tmpl, 1, 0, "tmpl-proxy.example.com", 9999);
|
|
int conn = sceHttpCreateConnection(tmpl, "x", "http", 80, 0);
|
|
// Override on connection
|
|
EXPECT_EQ(sceHttpSetProxy(conn, 1, 0, "conn-proxy.example.com", 8888), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// sceHttpsLoadCert / sceHttpsUnloadCert tests
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpsLoadCert(int, int, const void**, const void*, const void*);
|
|
int PS4_SYSV_ABI sceHttpsUnloadCert(int);
|
|
} // namespace Libraries::Http
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, LoadCertHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
const void* dummy_cas[2] = {nullptr, nullptr};
|
|
EXPECT_EQ(sceHttpsLoadCert(ctx, 2, dummy_cas, nullptr, nullptr), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, LoadCertBeforeInit) {
|
|
EXPECT_EQ(sceHttpsLoadCert(1, 0, nullptr, nullptr, nullptr),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, LoadCertInvalidCtxId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpsLoadCert(99999, 0, nullptr, nullptr, nullptr),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, UnloadCertHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
const void* dummy_cas[1] = {nullptr};
|
|
sceHttpsLoadCert(ctx, 1, dummy_cas, nullptr, nullptr);
|
|
EXPECT_EQ(sceHttpsUnloadCert(ctx), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, UnloadCertWithoutLoadIsOk) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
// Idempotent: erasing from the set returns 0 but we don't surface it as
|
|
// an error since context exists.
|
|
EXPECT_EQ(sceHttpsUnloadCert(ctx), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, UnloadCertBeforeInit) {
|
|
EXPECT_EQ(sceHttpsUnloadCert(1), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, UnloadCertInvalidCtxId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpsUnloadCert(99999), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, LoadCertSurvivesTermCleanly) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
const void* dummy[1] = {nullptr};
|
|
EXPECT_EQ(sceHttpsLoadCert(ctx, 1, dummy, nullptr, nullptr), ORBIS_OK);
|
|
sceHttpTerm(ctx);
|
|
// After Term, the ctxId is invalid - LoadCert on it must fail.
|
|
EXPECT_EQ(sceHttpsLoadCert(ctx, 1, dummy, nullptr, nullptr),
|
|
static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// sceHttpsGetCaList / sceHttpsFreeCaList tests
|
|
|
|
namespace Libraries::Http {
|
|
int PS4_SYSV_ABI sceHttpsGetCaList(int httpCtxId, OrbisHttpsCaList* list);
|
|
int PS4_SYSV_ABI sceHttpsFreeCaList(int libhttpCtxId, OrbisHttpsCaList* caList);
|
|
} // namespace Libraries::Http
|
|
|
|
namespace {
|
|
|
|
TEST_F(HttpLifecycle, GetCaListHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpsCaList list{};
|
|
list.certsNum = 999; // sentinel; should be reset to 0
|
|
EXPECT_EQ(sceHttpsGetCaList(ctx, &list), ORBIS_OK);
|
|
EXPECT_EQ(list.certsNum, 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, GetCaListBeforeInit) {
|
|
OrbisHttpsCaList list{};
|
|
EXPECT_EQ(sceHttpsGetCaList(1, &list), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, GetCaListInvalidCtxId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpsCaList list{};
|
|
EXPECT_EQ(sceHttpsGetCaList(99999, &list), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, GetCaListNullList) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpsGetCaList(ctx, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, FreeCaListHappyPath) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpsCaList list{};
|
|
list.certsNum = 5; // anything non-zero
|
|
EXPECT_EQ(sceHttpsFreeCaList(ctx, &list), ORBIS_OK);
|
|
EXPECT_EQ(list.certsNum, 0);
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, FreeCaListBeforeInit) {
|
|
OrbisHttpsCaList list{};
|
|
EXPECT_EQ(sceHttpsFreeCaList(1, &list), static_cast<int>(ORBIS_HTTP_ERROR_BEFORE_INIT));
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, FreeCaListInvalidCtxId) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
OrbisHttpsCaList list{};
|
|
EXPECT_EQ(sceHttpsFreeCaList(99999, &list), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_ID));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
TEST_F(HttpLifecycle, FreeCaListNullList) {
|
|
int ctx = sceHttpInit(0, 0, 4096);
|
|
EXPECT_EQ(sceHttpsFreeCaList(ctx, nullptr), static_cast<int>(ORBIS_HTTP_ERROR_INVALID_VALUE));
|
|
sceHttpTerm(ctx);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Libraries::Http {
|
|
bool IsFollowableRedirect(int status, s32 method);
|
|
s32 MethodAfterRedirect(int status, s32 original_method);
|
|
struct ResolvedRedirect {
|
|
std::string scheme;
|
|
std::string host;
|
|
u16 port;
|
|
std::string path;
|
|
};
|
|
std::optional<ResolvedRedirect> ResolveRedirectLocation(const std::string& current_scheme,
|
|
const std::string& current_host,
|
|
u16 current_port,
|
|
std::string_view location);
|
|
} // namespace Libraries::Http
|
|
|
|
namespace {
|
|
|
|
using Libraries::Http::IsFollowableRedirect;
|
|
using Libraries::Http::MethodAfterRedirect;
|
|
using Libraries::Http::ResolveRedirectLocation;
|
|
|
|
// --- IsFollowableRedirect: status filter ---
|
|
|
|
TEST(Ps4Redirect, FollowsThreeHundred) {
|
|
EXPECT_TRUE(IsFollowableRedirect(300, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
TEST(Ps4Redirect, FollowsThreeOhOne) {
|
|
EXPECT_TRUE(IsFollowableRedirect(301, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
TEST(Ps4Redirect, FollowsThreeOhTwo) {
|
|
EXPECT_TRUE(IsFollowableRedirect(302, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
TEST(Ps4Redirect, FollowsThreeOhThree) {
|
|
EXPECT_TRUE(IsFollowableRedirect(303, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
TEST(Ps4Redirect, FollowsThreeOhSeven) {
|
|
EXPECT_TRUE(IsFollowableRedirect(307, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
TEST(Ps4Redirect, RejectsThreeOhFour) {
|
|
EXPECT_FALSE(IsFollowableRedirect(304, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
TEST(Ps4Redirect, RejectsThreeOhFive) {
|
|
EXPECT_FALSE(IsFollowableRedirect(305, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
TEST(Ps4Redirect, RejectsThreeOhSix) {
|
|
EXPECT_FALSE(IsFollowableRedirect(306, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
TEST(Ps4Redirect, RejectsThreeOhEight) {
|
|
EXPECT_FALSE(IsFollowableRedirect(308, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
TEST(Ps4Redirect, RejectsNonRedirect) {
|
|
EXPECT_FALSE(IsFollowableRedirect(200, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
TEST(Ps4Redirect, RejectsServerError) {
|
|
EXPECT_FALSE(IsFollowableRedirect(500, ORBIS_HTTP_METHOD_GET));
|
|
}
|
|
|
|
// --- IsFollowableRedirect: POST nuance (only 303 follows) ---
|
|
|
|
TEST(Ps4Redirect, PostFollows303) {
|
|
EXPECT_TRUE(IsFollowableRedirect(303, ORBIS_HTTP_METHOD_POST));
|
|
}
|
|
TEST(Ps4Redirect, PostDoesNotFollow300) {
|
|
EXPECT_FALSE(IsFollowableRedirect(300, ORBIS_HTTP_METHOD_POST));
|
|
}
|
|
TEST(Ps4Redirect, PostDoesNotFollow301) {
|
|
EXPECT_FALSE(IsFollowableRedirect(301, ORBIS_HTTP_METHOD_POST));
|
|
}
|
|
TEST(Ps4Redirect, PostDoesNotFollow302) {
|
|
EXPECT_FALSE(IsFollowableRedirect(302, ORBIS_HTTP_METHOD_POST));
|
|
}
|
|
TEST(Ps4Redirect, PostDoesNotFollow307) {
|
|
EXPECT_FALSE(IsFollowableRedirect(307, ORBIS_HTTP_METHOD_POST));
|
|
}
|
|
|
|
// --- IsFollowableRedirect: HEAD always follows where status allows ---
|
|
|
|
TEST(Ps4Redirect, HeadFollows301) {
|
|
EXPECT_TRUE(IsFollowableRedirect(301, ORBIS_HTTP_METHOD_HEAD));
|
|
}
|
|
TEST(Ps4Redirect, HeadFollows303) {
|
|
EXPECT_TRUE(IsFollowableRedirect(303, ORBIS_HTTP_METHOD_HEAD));
|
|
}
|
|
TEST(Ps4Redirect, HeadFollows307) {
|
|
EXPECT_TRUE(IsFollowableRedirect(307, ORBIS_HTTP_METHOD_HEAD));
|
|
}
|
|
|
|
// --- MethodAfterRedirect: method change rules ---
|
|
|
|
TEST(Ps4Redirect, MethodPreservedOn301) {
|
|
EXPECT_EQ(MethodAfterRedirect(301, ORBIS_HTTP_METHOD_GET), ORBIS_HTTP_METHOD_GET);
|
|
EXPECT_EQ(MethodAfterRedirect(301, ORBIS_HTTP_METHOD_HEAD), ORBIS_HTTP_METHOD_HEAD);
|
|
EXPECT_EQ(MethodAfterRedirect(301, ORBIS_HTTP_METHOD_POST), ORBIS_HTTP_METHOD_POST);
|
|
}
|
|
TEST(Ps4Redirect, MethodPreservedOn302) {
|
|
EXPECT_EQ(MethodAfterRedirect(302, ORBIS_HTTP_METHOD_GET), ORBIS_HTTP_METHOD_GET);
|
|
EXPECT_EQ(MethodAfterRedirect(302, ORBIS_HTTP_METHOD_POST), ORBIS_HTTP_METHOD_POST);
|
|
}
|
|
TEST(Ps4Redirect, MethodPreservedOn307) {
|
|
// RFC 7231 §6.4.7 - 307 specifically preserves method
|
|
EXPECT_EQ(MethodAfterRedirect(307, ORBIS_HTTP_METHOD_GET), ORBIS_HTTP_METHOD_GET);
|
|
EXPECT_EQ(MethodAfterRedirect(307, ORBIS_HTTP_METHOD_POST), ORBIS_HTTP_METHOD_POST);
|
|
EXPECT_EQ(MethodAfterRedirect(307, ORBIS_HTTP_METHOD_HEAD), ORBIS_HTTP_METHOD_HEAD);
|
|
EXPECT_EQ(MethodAfterRedirect(307, ORBIS_HTTP_METHOD_PUT), ORBIS_HTTP_METHOD_PUT);
|
|
}
|
|
TEST(Ps4Redirect, PostDowngradesToGetOn303) {
|
|
EXPECT_EQ(MethodAfterRedirect(303, ORBIS_HTTP_METHOD_POST), ORBIS_HTTP_METHOD_GET);
|
|
}
|
|
TEST(Ps4Redirect, PutDowngradesToGetOn303) {
|
|
EXPECT_EQ(MethodAfterRedirect(303, ORBIS_HTTP_METHOD_PUT), ORBIS_HTTP_METHOD_GET);
|
|
}
|
|
TEST(Ps4Redirect, HeadPreservedOn303) {
|
|
// RFC 7231 §6.4.4 carve-out: HEAD stays HEAD across 303
|
|
EXPECT_EQ(MethodAfterRedirect(303, ORBIS_HTTP_METHOD_HEAD), ORBIS_HTTP_METHOD_HEAD);
|
|
}
|
|
TEST(Ps4Redirect, GetStaysGetOn303) {
|
|
EXPECT_EQ(MethodAfterRedirect(303, ORBIS_HTTP_METHOD_GET), ORBIS_HTTP_METHOD_GET);
|
|
}
|
|
|
|
// --- ResolveRedirectLocation: URL parsing ---
|
|
|
|
TEST(Ps4Redirect, ResolveAbsoluteHttp) {
|
|
auto r =
|
|
ResolveRedirectLocation("https", "old.example.com", 443, "http://new.example.com/path?q=1");
|
|
ASSERT_TRUE(r.has_value());
|
|
EXPECT_EQ(r->scheme, "http");
|
|
EXPECT_EQ(r->host, "new.example.com");
|
|
EXPECT_EQ(r->port, 80u);
|
|
EXPECT_EQ(r->path, "/path?q=1");
|
|
}
|
|
TEST(Ps4Redirect, ResolveAbsoluteHttpsExplicitPort) {
|
|
auto r =
|
|
ResolveRedirectLocation("http", "old.example.com", 80, "https://secure.example.com:8443/x");
|
|
ASSERT_TRUE(r.has_value());
|
|
EXPECT_EQ(r->scheme, "https");
|
|
EXPECT_EQ(r->host, "secure.example.com");
|
|
EXPECT_EQ(r->port, 8443u);
|
|
EXPECT_EQ(r->path, "/x");
|
|
}
|
|
TEST(Ps4Redirect, ResolveAbsolutePath) {
|
|
auto r = ResolveRedirectLocation("https", "example.com", 443, "/new/path?q=1");
|
|
ASSERT_TRUE(r.has_value());
|
|
EXPECT_EQ(r->scheme, "https");
|
|
EXPECT_EQ(r->host, "example.com");
|
|
EXPECT_EQ(r->port, 443u);
|
|
EXPECT_EQ(r->path, "/new/path?q=1");
|
|
}
|
|
TEST(Ps4Redirect, ResolveAbsoluteWithoutPath) {
|
|
// Bare "https://host" with no path - default to "/"
|
|
auto r = ResolveRedirectLocation("http", "old", 80, "https://host.example.com");
|
|
ASSERT_TRUE(r.has_value());
|
|
EXPECT_EQ(r->scheme, "https");
|
|
EXPECT_EQ(r->host, "host.example.com");
|
|
EXPECT_EQ(r->port, 443u);
|
|
EXPECT_EQ(r->path, "/");
|
|
}
|
|
TEST(Ps4Redirect, ResolveSchemeUppercaseLowercased) {
|
|
auto r = ResolveRedirectLocation("http", "x", 80, "HTTPS://host/p");
|
|
ASSERT_TRUE(r.has_value());
|
|
EXPECT_EQ(r->scheme, "https"); // canonicalised
|
|
}
|
|
TEST(Ps4Redirect, ResolveRejectsDocumentRelative) {
|
|
// "foo/bar" - no leading slash, no scheme
|
|
auto r = ResolveRedirectLocation("https", "x", 443, "foo/bar");
|
|
EXPECT_FALSE(r.has_value());
|
|
}
|
|
TEST(Ps4Redirect, ResolveRejectsEmpty) {
|
|
auto r = ResolveRedirectLocation("https", "x", 443, "");
|
|
EXPECT_FALSE(r.has_value());
|
|
}
|
|
TEST(Ps4Redirect, ResolveRejectsUnknownScheme) {
|
|
// ftp:// is not a scheme libhttp speaks; we bail
|
|
auto r = ResolveRedirectLocation("https", "x", 443, "ftp://host/path");
|
|
EXPECT_FALSE(r.has_value());
|
|
}
|
|
TEST(Ps4Redirect, ResolveRejectsInvalidPort) {
|
|
auto r = ResolveRedirectLocation("https", "x", 443, "https://host:99999/path");
|
|
EXPECT_FALSE(r.has_value());
|
|
}
|
|
TEST(Ps4Redirect, ResolveRejectsZeroPort) {
|
|
auto r = ResolveRedirectLocation("https", "x", 443, "https://host:0/path");
|
|
EXPECT_FALSE(r.has_value());
|
|
}
|
|
TEST(Ps4Redirect, ResolveRejectsEmptyAuthority) {
|
|
auto r = ResolveRedirectLocation("https", "x", 443, "https:///path");
|
|
EXPECT_FALSE(r.has_value());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Libraries::Http {
|
|
struct HostOverrideTarget {
|
|
std::string scheme;
|
|
std::string host;
|
|
u16 port = 0;
|
|
};
|
|
std::unordered_map<std::string, HostOverrideTarget> ParseHostOverridesJson(
|
|
const std::string& json_text);
|
|
bool ApplyHostOverride(std::string& scheme, std::string& host, u16& port, bool& is_secure);
|
|
} // namespace Libraries::Http
|
|
|
|
namespace {
|
|
|
|
using Libraries::Http::ApplyHostOverride;
|
|
using Libraries::Http::ParseHostOverridesJson;
|
|
|
|
// --- ParseHostOverridesJson: shape parsing ---
|
|
|
|
TEST(HostOverride, ParseEmptyStringYieldsEmptyMap) {
|
|
auto m = ParseHostOverridesJson("");
|
|
EXPECT_TRUE(m.empty());
|
|
}
|
|
|
|
TEST(HostOverride, ParseEmptyObjectYieldsEmptyMap) {
|
|
auto m = ParseHostOverridesJson("{}");
|
|
EXPECT_TRUE(m.empty());
|
|
}
|
|
|
|
TEST(HostOverride, ParseSingleHostNoPortNoScheme) {
|
|
auto m = ParseHostOverridesJson(R"({"api.example.com": "localhost"})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
const auto& e = m.at("api.example.com");
|
|
EXPECT_EQ(e.scheme, ""); // preserve original
|
|
EXPECT_EQ(e.host, "localhost");
|
|
EXPECT_EQ(e.port, 0); // preserve original port
|
|
}
|
|
|
|
TEST(HostOverride, ParseSingleHostWithPortNoScheme) {
|
|
auto m = ParseHostOverridesJson(R"({"api.example.com": "localhost:8080"})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
const auto& e = m.at("api.example.com");
|
|
EXPECT_EQ(e.scheme, "");
|
|
EXPECT_EQ(e.host, "localhost");
|
|
EXPECT_EQ(e.port, 8080);
|
|
}
|
|
|
|
TEST(HostOverride, ParseHttpSchemeWithPort) {
|
|
auto m = ParseHostOverridesJson(R"({"api.example.com": "http://localhost:8080"})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
const auto& e = m.at("api.example.com");
|
|
EXPECT_EQ(e.scheme, "http");
|
|
EXPECT_EQ(e.host, "localhost");
|
|
EXPECT_EQ(e.port, 8080);
|
|
}
|
|
|
|
TEST(HostOverride, ParseHttpsSchemeWithPort) {
|
|
auto m = ParseHostOverridesJson(R"({"api.example.com": "https://secure.local:8443"})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
const auto& e = m.at("api.example.com");
|
|
EXPECT_EQ(e.scheme, "https");
|
|
EXPECT_EQ(e.host, "secure.local");
|
|
EXPECT_EQ(e.port, 8443);
|
|
}
|
|
|
|
TEST(HostOverride, ParseHttpSchemeNoPort) {
|
|
auto m = ParseHostOverridesJson(R"({"api.example.com": "http://localhost"})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
const auto& e = m.at("api.example.com");
|
|
EXPECT_EQ(e.scheme, "http");
|
|
EXPECT_EQ(e.host, "localhost");
|
|
EXPECT_EQ(e.port, 0);
|
|
}
|
|
|
|
TEST(HostOverride, ParseSchemeUppercaseLowercased) {
|
|
auto m = ParseHostOverridesJson(R"({"api.example.com": "HTTPS://Host:443"})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
EXPECT_EQ(m.at("api.example.com").scheme, "https");
|
|
}
|
|
|
|
TEST(HostOverride, ParseUnknownSchemeDropsSchemeKeepsHost) {
|
|
// ftp:// is not http/https; scheme is silently dropped, host still parsed.
|
|
auto m = ParseHostOverridesJson(R"({"api.example.com": "ftp://elsewhere:21"})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
const auto& e = m.at("api.example.com");
|
|
EXPECT_EQ(e.scheme, "");
|
|
EXPECT_EQ(e.host, "elsewhere");
|
|
EXPECT_EQ(e.port, 21);
|
|
}
|
|
|
|
TEST(HostOverride, ParseMultipleEntriesMixedForms) {
|
|
auto m = ParseHostOverridesJson(R"({
|
|
"api.example.com": "http://localhost:8080",
|
|
"analytics.example.com": "https://127.0.0.1:9090",
|
|
"static.example.com": "mock.local:7000",
|
|
"*": "http://catch-all.local"
|
|
})");
|
|
ASSERT_EQ(m.size(), 4u);
|
|
EXPECT_EQ(m.at("api.example.com").scheme, "http");
|
|
EXPECT_EQ(m.at("api.example.com").port, 8080);
|
|
EXPECT_EQ(m.at("analytics.example.com").scheme, "https");
|
|
EXPECT_EQ(m.at("static.example.com").scheme, ""); // no scheme prefix
|
|
EXPECT_EQ(m.at("static.example.com").port, 7000);
|
|
EXPECT_EQ(m.at("*").scheme, "http");
|
|
EXPECT_EQ(m.at("*").port, 0); // no port = preserve / default
|
|
}
|
|
|
|
TEST(HostOverride, ParseCatchAllWildcard) {
|
|
auto m = ParseHostOverridesJson(R"({"*": "http://localhost:8080"})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
EXPECT_TRUE(m.contains("*"));
|
|
EXPECT_EQ(m.at("*").host, "localhost");
|
|
EXPECT_EQ(m.at("*").scheme, "http");
|
|
}
|
|
|
|
TEST(HostOverride, ParseInvalidJsonYieldsEmpty) {
|
|
auto m = ParseHostOverridesJson("this is not json");
|
|
EXPECT_TRUE(m.empty());
|
|
}
|
|
|
|
TEST(HostOverride, ParseNonObjectRootYieldsEmpty) {
|
|
auto m = ParseHostOverridesJson(R"(["array", "not", "object"])");
|
|
EXPECT_TRUE(m.empty());
|
|
}
|
|
|
|
TEST(HostOverride, ParseSkipsUnderscorePrefixedKeys) {
|
|
auto m = ParseHostOverridesJson(R"({
|
|
"_comment": "this is a comment block",
|
|
"_section_apex": "--- Apex Legends endpoints ---",
|
|
"api.example.com": "http://localhost:8080",
|
|
"_1": "another comment",
|
|
"*": "http://catch-all.local"
|
|
})");
|
|
ASSERT_EQ(m.size(), 2u);
|
|
EXPECT_TRUE(m.contains("api.example.com"));
|
|
EXPECT_TRUE(m.contains("*"));
|
|
EXPECT_FALSE(m.contains("_comment"));
|
|
EXPECT_FALSE(m.contains("_section_apex"));
|
|
EXPECT_FALSE(m.contains("_1"));
|
|
}
|
|
|
|
TEST(HostOverride, ParseSkipsNonStringValues) {
|
|
auto m = ParseHostOverridesJson(R"({
|
|
"good.example.com": "http://localhost:8080",
|
|
"bad-number.example.com": 1234,
|
|
"bad-null.example.com": null,
|
|
"bad-object.example.com": {"nested": "no"}
|
|
})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
EXPECT_TRUE(m.contains("good.example.com"));
|
|
}
|
|
|
|
TEST(HostOverride, ParseSkipsEmptyStringValues) {
|
|
auto m = ParseHostOverridesJson(R"({
|
|
"good.example.com": "localhost",
|
|
"empty.example.com": ""
|
|
})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
EXPECT_TRUE(m.contains("good.example.com"));
|
|
}
|
|
|
|
TEST(HostOverride, ParseBadPortKeepsHostDropsPort) {
|
|
auto m = ParseHostOverridesJson(R"({
|
|
"api.example.com": "http://localhost:not-a-number"
|
|
})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
EXPECT_EQ(m.at("api.example.com").scheme, "http");
|
|
EXPECT_EQ(m.at("api.example.com").host, "localhost");
|
|
EXPECT_EQ(m.at("api.example.com").port, 0);
|
|
}
|
|
|
|
TEST(HostOverride, ParseOutOfRangePortKeepsHostDropsPort) {
|
|
auto m = ParseHostOverridesJson(R"({
|
|
"api.example.com": "localhost:99999"
|
|
})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
EXPECT_EQ(m.at("api.example.com").host, "localhost");
|
|
EXPECT_EQ(m.at("api.example.com").port, 0);
|
|
}
|
|
|
|
TEST(HostOverride, ParseZeroPortKeepsHostDropsPort) {
|
|
auto m = ParseHostOverridesJson(R"({
|
|
"api.example.com": "localhost:0"
|
|
})");
|
|
ASSERT_EQ(m.size(), 1u);
|
|
EXPECT_EQ(m.at("api.example.com").host, "localhost");
|
|
EXPECT_EQ(m.at("api.example.com").port, 0);
|
|
}
|
|
|
|
// --- ApplyHostOverride: behavior with no JSON file present ---
|
|
static bool HostOverrideJsonConfigured() {
|
|
if (const char* p = std::getenv("SHADPS4_HTTP_HOST_OVERRIDES_JSON"); p && p[0]) {
|
|
return true;
|
|
}
|
|
std::ifstream f("host_overrides.json");
|
|
return f.is_open();
|
|
}
|
|
|
|
TEST(HostOverride, InactiveByDefaultLeavesValuesUnchanged) {
|
|
if (HostOverrideJsonConfigured()) {
|
|
GTEST_SKIP() << "host overrides JSON configured; off-path test inapplicable";
|
|
}
|
|
std::string scheme = "https";
|
|
std::string host = "api.example.com";
|
|
u16 port = 443;
|
|
bool is_secure = true;
|
|
const bool changed = ApplyHostOverride(scheme, host, port, is_secure);
|
|
EXPECT_FALSE(changed);
|
|
EXPECT_EQ(scheme, "https");
|
|
EXPECT_EQ(host, "api.example.com");
|
|
EXPECT_EQ(port, 443);
|
|
EXPECT_TRUE(is_secure);
|
|
}
|
|
|
|
TEST(HostOverride, InactivePreservesHttpScheme) {
|
|
if (HostOverrideJsonConfigured()) {
|
|
GTEST_SKIP() << "host overrides JSON configured; off-path test inapplicable";
|
|
}
|
|
std::string scheme = "http";
|
|
std::string host = "plain.example.com";
|
|
u16 port = 80;
|
|
bool is_secure = false;
|
|
const bool changed = ApplyHostOverride(scheme, host, port, is_secure);
|
|
EXPECT_FALSE(changed);
|
|
EXPECT_EQ(scheme, "http");
|
|
EXPECT_EQ(host, "plain.example.com");
|
|
EXPECT_EQ(port, 80);
|
|
EXPECT_FALSE(is_secure);
|
|
}
|
|
|
|
TEST(HostOverride, InactivePreservesUnusualPort) {
|
|
if (HostOverrideJsonConfigured()) {
|
|
GTEST_SKIP() << "host overrides JSON configured; off-path test inapplicable";
|
|
}
|
|
std::string scheme = "https";
|
|
std::string host = "custom-port.example.com";
|
|
u16 port = 5300; // Pinball FX uses this for kensho-discovery
|
|
bool is_secure = true;
|
|
ApplyHostOverride(scheme, host, port, is_secure);
|
|
EXPECT_EQ(port, 5300);
|
|
}
|
|
|
|
// --- ApplyHostOverride: behavior WITH the test JSON file pre-staged ---
|
|
static const char* kTestOverrideJson = R"({
|
|
"api.example.com": "localhost:8080",
|
|
"*": "mock.local:8443"
|
|
})";
|
|
|
|
TEST(HostOverride, ActiveExactHostMatchFromJsonFile) {
|
|
if (!HostOverrideJsonConfigured()) {
|
|
GTEST_SKIP() << "host overrides JSON not configured; on-path test inapplicable";
|
|
}
|
|
std::string scheme = "https";
|
|
std::string host = "api.example.com";
|
|
u16 port = 443;
|
|
bool is_secure = true;
|
|
const bool changed = ApplyHostOverride(scheme, host, port, is_secure);
|
|
EXPECT_TRUE(changed);
|
|
const bool any_change = (host != "api.example.com") || (port != 443);
|
|
EXPECT_TRUE(any_change);
|
|
}
|
|
|
|
} // namespace
|