diff --git a/src/core/libraries/network/http.cpp b/src/core/libraries/network/http.cpp index 2e90c3996..6282fb10a 100644 --- a/src/core/libraries/network/http.cpp +++ b/src/core/libraries/network/http.cpp @@ -3,16 +3,19 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include #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 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& 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 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 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 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 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 req_ptr; + { + std::lock_guard 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(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 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(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 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 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 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(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(to_copy); +} + +int PS4_SYSV_ABI sceHttpAbortRequest(int reqId) { + LOG_INFO(Lib_Http, "called reqId={}", reqId); + std::lock_guard 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 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(wr)); + } + return wr; + } + if (req.res.all_headers_blob.empty()) { + *header = nullptr; + *headerSize = 0; + } else { + *header = const_cast(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 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(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 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(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(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 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 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 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 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 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 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) { diff --git a/src/core/libraries/network/http.h b/src/core/libraries/network/http.h index fc626eb08..0fdc2033e 100644 --- a/src/core/libraries/network/http.h +++ b/src/core/libraries/network/http.h @@ -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,