http part5 (#4453)

* added async state sending support

* typo

* implemented blocking

* clang is not my friend

* fixed stephen's review

* code organize / nothing changed
This commit is contained in:
georgemoralis 2026-05-21 17:14:03 +03:00 committed by GitHub
parent 03ebac577b
commit 82beca2bbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 452 additions and 197 deletions

View File

@ -3,16 +3,19 @@
#include <atomic>
#include <cctype>
#include <condition_variable>
#include <cstring>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/orbis_error.h"
#include "core/libraries/libs.h"
@ -35,6 +38,7 @@ struct HttpSettings {
bool auto_redirect = true;
bool inflate_gzip = true;
u32 ssl_flags = ORBIS_HTTPS_FLAG_SDK_DEFAULT; // SSL flag mask. Bitmask of OrbisHttpsFlags.
bool nonblock = false; // false = blocking (default), true = nonblock (EAGAIN)
};
struct HttpTemplate {
@ -56,6 +60,15 @@ struct HttpConnection {
HttpSettings settings;
};
struct HttpResponse {
int status_code = 0;
u64 content_length = 0;
int content_length_result = ORBIS_HTTP_ERROR_NO_CONTENT_LENGTH;
std::vector<u8> body;
u64 read_cursor = 0;
std::string all_headers_blob; // Pre-formatted "Name: Value\r\n..." string.
};
struct HttpRequest {
int conn_id = 0;
int method = 0;
@ -66,6 +79,9 @@ struct HttpRequest {
bool deleted = false;
s32 last_errno = 0; // populated by SendRequest, read by GetLastErrno
HttpSettings settings;
HttpResponse res;
std::condition_variable cv; // waiters in blocking getters block on this
// notified when state leaves Sending.
};
struct HttpState {
@ -82,6 +98,10 @@ struct HttpState {
static HttpState g_state;
//***********************************
// Helper functions
//***********************************
static HttpSettings* ResolveSettings(int id, const char*& level) {
if (auto it = g_state.templates.find(id); it != g_state.templates.end()) {
level = "template";
@ -99,6 +119,40 @@ static HttpSettings* ResolveSettings(int id, const char*& level) {
return nullptr;
}
// Populate a response object with the shape a transport-level failure produces:
// no status line, no headers, no body. Used by the no-internet path.
static void SynthesizeTransportFailureResponse(HttpResponse& res) {
res.status_code = 0;
res.content_length = 0;
res.content_length_result = ORBIS_HTTP_ERROR_NO_CONTENT_LENGTH;
res.body.clear();
res.read_cursor = 0;
res.all_headers_blob.clear();
}
static int WaitForResponseReady(HttpRequest& req, std::unique_lock<std::mutex>& lock) {
if (req.state == HttpRequestState::Aborted) {
return ORBIS_HTTP_ERROR_ABORTED;
}
if (req.state == HttpRequestState::Created) {
return ORBIS_HTTP_ERROR_BEFORE_SEND;
}
if (req.state == HttpRequestState::Sent) {
return ORBIS_OK;
}
// state == Sending. Honor nonblock: return EAGAIN instead of blocking.
if (req.settings.nonblock) {
return ORBIS_HTTP_ERROR_EAGAIN;
}
req.cv.wait(lock, [&req]() {
return req.state != HttpRequestState::Sending || g_state.shutting_down.load();
});
if (g_state.shutting_down.load() || req.state == HttpRequestState::Aborted) {
return ORBIS_HTTP_ERROR_ABORTED;
}
return ORBIS_OK;
}
void NormalizeAndAppendPath(char* dest, char* src) {
char* lastSlash;
u64 length;
@ -160,15 +214,9 @@ static bool ContainsCrLf(const char* s) {
return false;
}
int PS4_SYSV_ABI sceHttpAbortRequest(int reqId) {
LOG_ERROR(Lib_Http, "(STUBBED) called reqId={}", reqId);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpAbortRequestForce(int reqId) {
LOG_ERROR(Lib_Http, "(STUBBED) called reqId={}", reqId);
return ORBIS_OK;
}
//***********************************
// TODO/WIP/Stubbed functions
//***********************************
int PS4_SYSV_ABI sceHttpAbortWaitRequest(OrbisHttpEpollHandle eh) {
LOG_ERROR(Lib_Http, "(STUBBED) called eh={}", fmt::ptr(eh));
@ -493,53 +541,6 @@ int PS4_SYSV_ABI sceHttpDbgShowStat() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpDeleteConnection(int connId) {
LOG_INFO(Lib_Http, "called connId={}", connId);
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
if (g_state.connections.erase(connId) == 0) {
LOG_ERROR(Lib_Http, "Invalid connId={}", connId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpDeleteRequest(int reqId) {
LOG_INFO(Lib_Http, "called reqId={}", reqId);
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
auto it = g_state.requests.find(reqId);
if (it == g_state.requests.end()) {
LOG_ERROR(Lib_Http, "Invalid reqId={}", reqId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
auto req_ptr = it->second;
req_ptr->deleted = true;
req_ptr->state = HttpRequestState::Aborted;
g_state.requests.erase(it);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpDeleteTemplate(int tmplId) {
LOG_INFO(Lib_Http, "called tmplId={}", tmplId);
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
if (g_state.templates.erase(tmplId) == 0) {
LOG_ERROR(Lib_Http, "Invalid tmplId={}", tmplId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpDestroyEpoll(int libhttpCtxId, OrbisHttpEpollHandle eh) {
LOG_ERROR(Lib_Http, "(STUBBED) called libhttpCtxId={}, eh={}", libhttpCtxId, fmt::ptr(eh));
return ORBIS_OK;
@ -550,12 +551,6 @@ int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled(int id, int* isEnable) {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize) {
LOG_ERROR(Lib_Http, "(STUBBED) called reqId={}, header={}, headerSize={}", reqId,
fmt::ptr(header), fmt::ptr(headerSize));
return ORBIS_FAIL;
}
int PS4_SYSV_ABI sceHttpGetAuthEnabled(int id, int* isEnable) {
LOG_ERROR(Lib_Http, "(STUBBED) called id={}, isEnable={}", id, fmt::ptr(isEnable));
return ORBIS_OK;
@ -605,57 +600,11 @@ int PS4_SYSV_ABI sceHttpGetMemoryPoolStats(int libhttpCtxId,
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetNonblock(int id, int* isEnable) {
LOG_ERROR(Lib_Http, "(STUBBED) called id={}, isEnable={}", id, fmt::ptr(isEnable));
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetRegisteredCtxIds() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetResponseContentLength(int reqId, int* result, u64* contentLength) {
LOG_ERROR(Lib_Http, "(STUBBED) called reqId={}, result={}, contentLength={}", reqId,
fmt::ptr(result), fmt::ptr(contentLength));
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode) {
LOG_INFO(Lib_Http, "(STUBBED) called reqId={}, statusCode={}", reqId, fmt::ptr(statusCode));
#if 0
if (!g_state.inited)
return ORBIS_HTTP_ERROR_BEFORE_INIT;
if (statusCode == nullptr)
return ORBIS_HTTP_ERROR_INVALID_VALUE;
int ret = 0;
// Lookup HttpRequestInternal by reqId
HttpRequestInternal* request = nullptr;
ret = HttpRequestInternal_Acquire(&request, reqId);
if (ret < 0)
return ret;
request->m_mutex.lock();
if (request->state > 0x11) {
if (request->state == 0x16) {
ret = request->errorCode;
} else {
*statusCode = request->httpStatusCode;
ret = 0;
}
} else {
ret = ORBIS_HTTP_ERROR_BEFORE_SEND;
}
request->m_mutex.unlock();
HttpRequestInternal_Release(request);
return ret;
#else
return ORBIS_HTTP_ERROR_BEFORE_SEND;
#endif
}
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize) {
LOG_INFO(Lib_Http, "called libnetMemId={}, libsslCtxId={}, poolSize={}", libnetMemId,
libsslCtxId, poolSize);
@ -673,11 +622,6 @@ int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize) {
return ctx_id;
}
int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size) {
LOG_ERROR(Lib_Http, "(STUBBED) called reqId={}, data={}, size={}", reqId, fmt::ptr(data), size);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpRedirectCacheFlush(int libhttpCtxId) {
LOG_ERROR(Lib_Http, "(STUBBED) called libhttpCtxId={}", libhttpCtxId);
return ORBIS_OK;
@ -695,31 +639,59 @@ int PS4_SYSV_ABI sceHttpRequestGetAllHeaders() {
int PS4_SYSV_ABI sceHttpSendRequest(int reqId, const void* postData, u64 size) {
LOG_INFO(Lib_Http, "called reqId={}, postData={}, size={}", reqId, fmt::ptr(postData), size);
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
auto it = g_state.requests.find(reqId);
if (it == g_state.requests.end()) {
LOG_ERROR(Lib_Http, "Invalid reqId={}", reqId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
auto& req = *it->second;
if (req.state == HttpRequestState::Sent) {
LOG_ERROR(Lib_Http, "Request already sent (reqId={})", reqId);
return ORBIS_HTTP_ERROR_AFTER_SEND;
}
if (req.state == HttpRequestState::Aborted) {
LOG_ERROR(Lib_Http, "Request was aborted (reqId={})", reqId);
return ORBIS_HTTP_ERROR_ABORTED;
std::shared_ptr<HttpRequest> req_ptr;
{
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
auto it = g_state.requests.find(reqId);
if (it == g_state.requests.end()) {
LOG_ERROR(Lib_Http, "Invalid reqId={}", reqId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
auto& req = *it->second;
if (req.state == HttpRequestState::Sending || req.state == HttpRequestState::Sent) {
LOG_ERROR(Lib_Http, "Request already sent (reqId={})", reqId);
return ORBIS_HTTP_ERROR_AFTER_SEND;
}
if (req.state == HttpRequestState::Aborted) {
LOG_ERROR(Lib_Http, "Request was aborted (reqId={})", reqId);
return ORBIS_HTTP_ERROR_ABORTED;
}
// Created -> Sending. Worker thread will move to Sent.
req.state = HttpRequestState::Sending;
req_ptr = it->second;
}
req.last_errno = ORBIS_HTTP_ERROR_RESOLVER_ENODNS;
req.state = HttpRequestState::Sent;
LOG_INFO(Lib_Http, "reqId={} send failed: last_errno={:#x} (no-internet path)", reqId,
static_cast<u32>(req.last_errno));
return ORBIS_HTTP_ERROR_RESOLVER_ENODNS;
LOG_INFO(Lib_Http, "reqId={} dispatched to async worker [{}]", reqId,
EmulatorSettings.IsConnectedToNetwork() ? "ONLINE (TODO real I/O)"
: "OFFLINE no-internet path");
std::thread([req_ptr, reqId]() {
HttpResponse local_res;
if (!EmulatorSettings.IsConnectedToNetwork()) {
SynthesizeTransportFailureResponse(local_res);
} else {
// TODO: real network I/O path but for now return the same so switching doesn't affect
// something
SynthesizeTransportFailureResponse(local_res);
}
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (g_state.shutting_down.load() || req_ptr->deleted ||
req_ptr->state == HttpRequestState::Aborted) {
req_ptr->cv.notify_all();
return;
}
req_ptr->res = std::move(local_res);
req_ptr->state = HttpRequestState::Sent;
req_ptr->last_errno = ORBIS_HTTP_ERROR_RESOLVER_ENODNS;
LOG_INFO(Lib_Http, "(TRANSPORT FAIL) reqId={} -> 0 (body 0 bytes, errno={:#x})", reqId,
static_cast<u32>(req_ptr->last_errno));
req_ptr->cv.notify_all();
}).detach();
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpSetAcceptEncodingGZIPEnabled(int id, int isEnable) {
@ -809,11 +781,6 @@ int PS4_SYSV_ABI sceHttpSetHttp09Enabled(int id, int isEnable) {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpSetNonblock(int id, int isEnable) {
LOG_ERROR(Lib_Http, "(STUBBED) called id={}, isEnable={}", id, isEnable);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpSetPolicyOption() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
@ -934,6 +901,7 @@ int PS4_SYSV_ABI sceHttpTerm(int libhttpCtxId) {
for (auto& [id, req_ptr] : g_state.requests) {
req_ptr->deleted = true;
req_ptr->state = HttpRequestState::Aborted;
req_ptr->cv.notify_all(); // wake blocked waiters before wiping the map
}
g_state.requests.clear();
g_state.connections.clear();
@ -946,16 +914,6 @@ int PS4_SYSV_ABI sceHttpTerm(int libhttpCtxId) {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpTryGetNonblock(int id, int* isEnable) {
LOG_ERROR(Lib_Http, "(STUBBED) called id={}, isEnable={}", id, fmt::ptr(isEnable));
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpTrySetNonblock(int id, int isEnable) {
LOG_ERROR(Lib_Http, "(STUBBED) called id={}, isEnable={}", id, isEnable);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpUnsetEpoll(int id) {
LOG_ERROR(Lib_Http, "(STUBBED) called id={}", id);
return ORBIS_OK;
@ -973,6 +931,127 @@ int PS4_SYSV_ABI sceHttpUriCopy() {
return ORBIS_OK;
}
//***********************************
// Non-blocking processing functions
//***********************************
int PS4_SYSV_ABI sceHttpGetNonblock(int id, int* isEnable) {
LOG_INFO(Lib_Http, "called id={}, isEnable={}", id, fmt::ptr(isEnable));
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
if (!isEnable) {
LOG_ERROR(Lib_Http, "isEnable output pointer is null");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
const char* level = "";
HttpSettings* s = ResolveSettings(id, level);
if (!s) {
LOG_ERROR(Lib_Http, "Invalid id={}", id);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
*isEnable = s->nonblock ? 1 : 0;
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpSetNonblock(int id, int isEnable) {
LOG_INFO(Lib_Http, "called id={}, isEnable={}", id, isEnable);
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
const char* level = "";
HttpSettings* s = ResolveSettings(id, level);
if (!s) {
LOG_ERROR(Lib_Http, "Invalid id={}", id);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
s->nonblock = (isEnable != 0);
LOG_INFO(Lib_Http, "set {} id={} nonblock={}", level, id, s->nonblock);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpTryGetNonblock(int id, int* isEnable) {
LOG_INFO(Lib_Http, "called id={}, isEnable={}", id, fmt::ptr(isEnable));
return sceHttpGetNonblock(id, isEnable);
}
int PS4_SYSV_ABI sceHttpTrySetNonblock(int id, int isEnable) {
LOG_INFO(Lib_Http, "called id={}, isEnable={}", id, isEnable);
return sceHttpSetNonblock(id, isEnable);
}
//***********************************
// Http Communication functions
//***********************************
int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size) {
LOG_INFO(Lib_Http, "called reqId={}, data={}, size={}", reqId, fmt::ptr(data), size);
std::unique_lock<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
if (!data) {
LOG_ERROR(Lib_Http, "data output pointer is null");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
auto it = g_state.requests.find(reqId);
if (it == g_state.requests.end()) {
LOG_ERROR(Lib_Http, "Invalid reqId={}", reqId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
auto& req = *it->second;
int wr = WaitForResponseReady(req, lock);
if (wr != ORBIS_OK) {
if (wr == ORBIS_HTTP_ERROR_EAGAIN) {
LOG_DEBUG(Lib_Http, "reqId={}: EAGAIN (response not yet ready)", reqId);
} else {
LOG_ERROR(Lib_Http, "Wait failed for reqId={}: {:#x}", reqId, static_cast<u32>(wr));
}
return wr;
}
u64 remaining = req.res.body.size() - req.res.read_cursor;
u64 to_copy = std::min(size, remaining);
if (to_copy > 0) {
std::memcpy(data, req.res.body.data() + req.res.read_cursor, to_copy);
req.res.read_cursor += to_copy;
}
LOG_INFO(Lib_Http, "reqId={} copied {} bytes (cursor {}/{}) ", reqId, to_copy,
req.res.read_cursor, req.res.body.size());
return static_cast<int>(to_copy);
}
int PS4_SYSV_ABI sceHttpAbortRequest(int reqId) {
LOG_INFO(Lib_Http, "called reqId={}", reqId);
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
auto it = g_state.requests.find(reqId);
if (it == g_state.requests.end()) {
LOG_ERROR(Lib_Http, "Invalid reqId={}", reqId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
auto& req = *it->second;
if (req.state == HttpRequestState::Created || req.state == HttpRequestState::Sending) {
req.state = HttpRequestState::Aborted;
req.cv.notify_all();
LOG_INFO(Lib_Http, "reqId={} marked Aborted", reqId);
} else {
LOG_INFO(Lib_Http, "reqId={} already Sent/Aborted, no-op", reqId);
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpAbortRequestForce(int reqId) {
LOG_INFO(Lib_Http, "called reqId={}", reqId);
return sceHttpAbortRequest(reqId);
}
//***********************************
// Https Option setting functions
//***********************************
@ -1075,6 +1154,112 @@ int PS4_SYSV_ABI sceHttpsEnableOptionPrivate(int id, u32 sslFlags) {
//***********************************
// Response Information functions
//***********************************
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize) {
LOG_INFO(Lib_Http, "called reqId={}, header={}, headerSize={}", reqId, fmt::ptr(header),
fmt::ptr(headerSize));
std::unique_lock<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
if (!header || !headerSize) {
LOG_ERROR(Lib_Http, "header or headerSize output pointer is null");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
auto it = g_state.requests.find(reqId);
if (it == g_state.requests.end()) {
LOG_ERROR(Lib_Http, "Invalid reqId={}", reqId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
auto& req = *it->second;
int wr = WaitForResponseReady(req, lock);
if (wr != ORBIS_OK) {
if (wr == ORBIS_HTTP_ERROR_EAGAIN) {
LOG_DEBUG(Lib_Http, "reqId={}: EAGAIN (response not yet ready)", reqId);
} else {
LOG_ERROR(Lib_Http, "Wait failed for reqId={}: {:#x}", reqId, static_cast<u32>(wr));
}
return wr;
}
if (req.res.all_headers_blob.empty()) {
*header = nullptr;
*headerSize = 0;
} else {
*header = const_cast<char*>(req.res.all_headers_blob.c_str());
*headerSize = req.res.all_headers_blob.size();
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetResponseContentLength(int reqId, int* result, u64* contentLength) {
LOG_INFO(Lib_Http, "called reqId={}, result={}, contentLength={}", reqId, fmt::ptr(result),
fmt::ptr(contentLength));
std::unique_lock<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
if (!result || !contentLength) {
LOG_ERROR(Lib_Http, "result or contentLength output pointer is null");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
auto it = g_state.requests.find(reqId);
if (it == g_state.requests.end()) {
LOG_ERROR(Lib_Http, "Invalid reqId={}", reqId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
auto& req = *it->second;
int wr = WaitForResponseReady(req, lock);
if (wr == ORBIS_HTTP_ERROR_EAGAIN) {
LOG_DEBUG(Lib_Http, "reqId={}: response not yet ready, returning BEFORE_SEND", reqId);
return ORBIS_HTTP_ERROR_BEFORE_SEND;
}
if (wr != ORBIS_OK) {
LOG_ERROR(Lib_Http, "Wait failed for reqId={}: {:#x}", reqId, static_cast<u32>(wr));
return wr;
}
*result = req.res.content_length_result;
*contentLength = req.res.content_length;
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode) {
LOG_INFO(Lib_Http, "called reqId={}, statusCode={}", reqId, fmt::ptr(statusCode));
std::unique_lock<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
if (!statusCode) {
LOG_ERROR(Lib_Http, "statusCode output pointer is null");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
auto it = g_state.requests.find(reqId);
if (it == g_state.requests.end()) {
LOG_ERROR(Lib_Http, "Invalid reqId={}", reqId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
auto& req = *it->second;
int wr = WaitForResponseReady(req, lock);
if (wr == ORBIS_HTTP_ERROR_EAGAIN) {
LOG_DEBUG(Lib_Http, "reqId={}: response not yet ready, returning BEFORE_SEND", reqId);
return ORBIS_HTTP_ERROR_BEFORE_SEND;
}
if (wr != ORBIS_OK) {
LOG_ERROR(Lib_Http, "Wait failed for reqId={}: {:#x}", reqId, static_cast<u32>(wr));
return wr;
}
// Transport failure: no status line was ever received from a server.
if (req.res.status_code == 0 && req.last_errno != 0) {
LOG_INFO(Lib_Http, "reqId={} transport failure, errno={:#x}, returning BEFORE_SEND", reqId,
static_cast<u32>(req.last_errno));
return ORBIS_HTTP_ERROR_BEFORE_SEND;
}
*statusCode = req.res.status_code;
LOG_INFO(Lib_Http, "reqId={} status={}", reqId, req.res.status_code);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpSetInflateGZIPEnabled(int id, int isEnable) {
LOG_INFO(Lib_Http, "called id={}, isEnable={}", id, isEnable);
std::lock_guard<std::mutex> lock(g_state.m_mutex);
@ -1216,9 +1401,63 @@ int PS4_SYSV_ABI sceHttpSetRecvTimeOut(int id, u32 usec) {
return ORBIS_OK;
}
//***********************************
// Connection functions
//***********************************
int PS4_SYSV_ABI sceHttpDeleteConnection(int connId) {
LOG_INFO(Lib_Http, "called connId={}", connId);
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
if (g_state.connections.erase(connId) == 0) {
LOG_ERROR(Lib_Http, "Invalid connId={}", connId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
return ORBIS_OK;
}
//***********************************
// Template functions
//***********************************
int PS4_SYSV_ABI sceHttpDeleteTemplate(int tmplId) {
LOG_INFO(Lib_Http, "called tmplId={}", tmplId);
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
if (g_state.templates.erase(tmplId) == 0) {
LOG_ERROR(Lib_Http, "Invalid tmplId={}", tmplId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
return ORBIS_OK;
}
//***********************************
// Request functions
//***********************************
int PS4_SYSV_ABI sceHttpDeleteRequest(int reqId) {
LOG_INFO(Lib_Http, "called reqId={}", reqId);
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
auto it = g_state.requests.find(reqId);
if (it == g_state.requests.end()) {
LOG_ERROR(Lib_Http, "Invalid reqId={}", reqId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
auto req_ptr = it->second;
req_ptr->deleted = true;
req_ptr->state = HttpRequestState::Aborted;
req_ptr->cv.notify_all();
g_state.requests.erase(it);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpCreateRequest2(int connId, const char* method, const char* path,
u64 contentLength) {
LOG_INFO(Lib_Http, "called connId={}, method={}, path={}, contentLength={}", connId,
@ -1241,42 +1480,47 @@ int PS4_SYSV_ABI sceHttpCreateRequest2(int connId, const char* method, const cha
return ORBIS_HTTP_METHOD_TRACE;
return -1;
};
// Resolve the connection's URL under the lock, then delegate.
// Resolve the connection's URL under the lock, then drop the lock before
// delegating to sceHttpCreateRequestWithURL
std::string url;
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
auto it = g_state.connections.find(connId);
if (it == g_state.connections.end()) {
LOG_ERROR(Lib_Http, "Invalid connId={}", connId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
if (!path) {
LOG_ERROR(Lib_Http, "path is null");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
if (ContainsCrLf(path)) {
LOG_ERROR(Lib_Http, "path contains CR/LF (CRLF-injection rejected): {}", path);
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
int int_method = map_method(method);
if (int_method < 0) {
if (!method) {
LOG_ERROR(Lib_Http, "method is null");
int int_method;
{
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
return ORBIS_HTTP_ERROR_BEFORE_INIT;
}
auto it = g_state.connections.find(connId);
if (it == g_state.connections.end()) {
LOG_ERROR(Lib_Http, "Invalid connId={}", connId);
return ORBIS_HTTP_ERROR_INVALID_ID;
}
if (!path) {
LOG_ERROR(Lib_Http, "path is null");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
LOG_INFO(Lib_Http, "method '{}' not in standard table; routing via CUSTOM slot", method);
int_method = ORBIS_HTTP_METHOD_CUSTOM;
}
const auto& conn = it->second;
url = conn.scheme + "://" + conn.hostname + ":" + std::to_string(conn.port);
if (path[0] != '\0') {
if (path[0] != '/') {
url.push_back('/');
if (ContainsCrLf(path)) {
LOG_ERROR(Lib_Http, "path contains CR/LF (CRLF-injection rejected): {}", path);
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
int_method = map_method(method);
if (int_method < 0) {
if (!method) {
LOG_ERROR(Lib_Http, "method is null");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
LOG_INFO(Lib_Http, "method '{}' not in standard table; routing via CUSTOM slot",
method);
int_method = ORBIS_HTTP_METHOD_CUSTOM;
}
const auto& conn = it->second;
url = conn.scheme + "://" + conn.hostname + ":" + std::to_string(conn.port);
if (path[0] != '\0') {
if (path[0] != '/') {
url.push_back('/');
}
url.append(path);
}
url.append(path);
}
int reqId = sceHttpCreateRequestWithURL(connId, int_method, url.c_str(), contentLength);
if (reqId > 0 && method) {

View File

@ -129,8 +129,6 @@ using OrbisHttpsCallback = int(PS4_SYSV_ABI*)(int libsslCtxId, u32 verifyErr, vo
using OrbisHttpsCaList = Libraries::Ssl::OrbisSslCaList;
// Functions
int PS4_SYSV_ABI sceHttpAbortRequest(int reqId);
int PS4_SYSV_ABI sceHttpAbortRequestForce(int reqId);
int PS4_SYSV_ABI sceHttpAbortWaitRequest(OrbisHttpEpollHandle eh);
int PS4_SYSV_ABI sceHttpAddCookie(int libhttpCtxId, const char* url, const char* cookie,
u64 cookieLength);
@ -162,12 +160,8 @@ int PS4_SYSV_ABI sceHttpDbgShowConnectionStat();
int PS4_SYSV_ABI sceHttpDbgShowMemoryPoolStat();
int PS4_SYSV_ABI sceHttpDbgShowRequestStat();
int PS4_SYSV_ABI sceHttpDbgShowStat();
int PS4_SYSV_ABI sceHttpDeleteConnection(int connId);
int PS4_SYSV_ABI sceHttpDeleteRequest(int reqId);
int PS4_SYSV_ABI sceHttpDeleteTemplate(int tmplId);
int PS4_SYSV_ABI sceHttpDestroyEpoll(int libhttpCtxId, OrbisHttpEpollHandle eh);
int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize);
int PS4_SYSV_ABI sceHttpGetAuthEnabled(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpGetConnectionStat();
int PS4_SYSV_ABI sceHttpGetCookie(int libhttpCtxId, const char* url, char* cookie, u64* required,
@ -177,12 +171,8 @@ int PS4_SYSV_ABI sceHttpGetCookieStats(int libhttpCtxId, OrbisHttpCookieStats* s
int PS4_SYSV_ABI sceHttpGetEpoll(int id, OrbisHttpEpollHandle* eh, void** userArg);
int PS4_SYSV_ABI sceHttpGetEpollId();
int PS4_SYSV_ABI sceHttpGetMemoryPoolStats(int libhttpCtxId, OrbisHttpMemoryPoolStats* currentStat);
int PS4_SYSV_ABI sceHttpGetNonblock(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpGetRegisteredCtxIds();
int PS4_SYSV_ABI sceHttpGetResponseContentLength(int reqId, int* result, u64* contentLength);
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode);
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize);
int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size);
int PS4_SYSV_ABI sceHttpRedirectCacheFlush(int libhttpCtxId);
int PS4_SYSV_ABI sceHttpRemoveRequestHeader(int id, const char* name);
int PS4_SYSV_ABI sceHttpRequestGetAllHeaders();
@ -206,7 +196,6 @@ int PS4_SYSV_ABI sceHttpSetDelayBuildRequestEnabled(int id, int isEnable);
int PS4_SYSV_ABI sceHttpSetEpoll(int id, OrbisHttpEpollHandle eh, void* userArg);
int PS4_SYSV_ABI sceHttpSetEpollId();
int PS4_SYSV_ABI sceHttpSetHttp09Enabled(int id, int isEnable);
int PS4_SYSV_ABI sceHttpSetNonblock(int id, int isEnable);
int PS4_SYSV_ABI sceHttpSetPolicyOption();
int PS4_SYSV_ABI sceHttpSetPriorityOption();
int PS4_SYSV_ABI sceHttpSetProxy();
@ -229,13 +218,23 @@ int PS4_SYSV_ABI sceHttpsSetSslCallback(int id, OrbisHttpsCallback cbfunc, void*
int PS4_SYSV_ABI sceHttpsSetSslVersion(int id, int version);
int PS4_SYSV_ABI sceHttpsUnloadCert(int libhttpCtxId);
int PS4_SYSV_ABI sceHttpTerm(int libhttpCtxId);
int PS4_SYSV_ABI sceHttpTryGetNonblock(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpTrySetNonblock(int id, int isEnable);
int PS4_SYSV_ABI sceHttpUnsetEpoll(int id);
int PS4_SYSV_ABI sceHttpWaitRequest(OrbisHttpEpollHandle eh, OrbisHttpNBEvent* nbev, int maxevents,
int timeout);
int PS4_SYSV_ABI sceHttpUriCopy();
//***********************************
// Non-blocking processing functions
//***********************************
int PS4_SYSV_ABI sceHttpGetNonblock(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpSetNonblock(int id, int isEnable);
int PS4_SYSV_ABI sceHttpTryGetNonblock(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpTrySetNonblock(int id, int isEnable);
//***********************************
// Http Communication functions
//***********************************
int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size);
int PS4_SYSV_ABI sceHttpAbortRequest(int reqId);
int PS4_SYSV_ABI sceHttpAbortRequestForce(int reqId);
//***********************************
// Https Option setting functions
//***********************************
@ -246,6 +245,9 @@ int PS4_SYSV_ABI sceHttpsEnableOptionPrivate(int id, u32 sslFlags);
//***********************************
// Response Information functions
//***********************************
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize);
int PS4_SYSV_ABI sceHttpGetResponseContentLength(int reqId, int* result, u64* contentLength);
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode);
int PS4_SYSV_ABI sceHttpSetInflateGZIPEnabled(int id, int isEnable);
//***********************************
// Http Header setting functions
@ -263,8 +265,17 @@ int PS4_SYSV_ABI sceHttpSetConnectTimeOut(int id, u32 usec);
int PS4_SYSV_ABI sceHttpSetSendTimeOut(int id, u32 usec);
int PS4_SYSV_ABI sceHttpSetRecvTimeOut(int id, u32 usec);
//***********************************
// Connection functions
//***********************************
int PS4_SYSV_ABI sceHttpDeleteConnection(int connId);
//***********************************
// Template functions
//***********************************
int PS4_SYSV_ABI sceHttpDeleteTemplate(int tmplId);
//***********************************
// Request functions
//***********************************
int PS4_SYSV_ABI sceHttpDeleteRequest(int reqId);
int PS4_SYSV_ABI sceHttpCreateRequest2(int connId, const char* method, const char* path,
u64 contentLength);
int PS4_SYSV_ABI sceHttpCreateRequestWithURL2(int connId, const char* method, const char* url,