// Vita3K emulator project // Copyright (C) 2025 Vita3K team // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #include #include #include #include #ifdef _WIN32 // windows moment #include #include #include #define write(x, y, z) _write(x, y, z) #define read(x, y, z) _read(x, y, z) #else #include #include #endif #include #include #include #include #include #include #include #include #include #include #include TRACY_MODULE_NAME(SceHttp); template <> std::string to_debug_str(const MemState &mem, SceHttpAddHeaderMode type) { switch (type) { case SCE_HTTP_HEADER_OVERWRITE: return "SCE_HTTP_HEADER_OVERWRITE"; case SCE_HTTP_HEADER_ADD: return "SCE_HTTP_HEADER_ADD"; } return std::to_string(type); } template <> std::string to_debug_str(const MemState &mem, SceHttpVersion type) { switch (type) { case SCE_HTTP_VERSION_1_0: return "SCE_HTTP_VERSION_1_0"; case SCE_HTTP_VERSION_1_1: return "SCE_HTTP_VERSION_1_1"; } return std::to_string(type); } template <> std::string to_debug_str(const MemState &mem, SceHttpMethods type) { switch (type) { case SCE_HTTP_METHOD_GET: return "SCE_HTTP_METHOD_GET"; case SCE_HTTP_METHOD_POST: return "SCE_HTTP_METHOD_POST"; case SCE_HTTP_METHOD_HEAD: return "SCE_HTTP_METHOD_HEAD"; case SCE_HTTP_METHOD_OPTIONS: return "SCE_HTTP_METHOD_OPTIONS"; case SCE_HTTP_METHOD_PUT: return "SCE_HTTP_METHOD_PUT"; case SCE_HTTP_METHOD_DELETE: return "SCE_HTTP_METHOD_DELETE"; case SCE_HTTP_METHOD_TRACE: return "SCE_HTTP_METHOD_TRACE"; case SCE_HTTP_METHOD_CONNECT: return "SCE_HTTP_METHOD_CONNECT"; case SCE_HTTP_METHOD_INVALID: break; // Invalid should fallback to raw decimal value } return std::to_string(type); } template <> std::string to_debug_str(const MemState &mem, SceHttpsFlags type) { std::string out; if (type & SCE_HTTPS_FLAG_SERVER_VERIFY) out += "SCE_HTTPS_FLAG_SERVER_VERIFY "; if (type & SCE_HTTPS_FLAG_CLIENT_VERIFY) out += "SCE_HTTPS_FLAG_CLIENT_VERIFY "; if (type & SCE_HTTPS_FLAG_CN_CHECK) out += "SCE_HTTPS_FLAG_CN_CHECK "; if (type & SCE_HTTPS_FLAG_NOT_AFTER_CHECK) out += "SCE_HTTPS_FLAG_NOT_AFTER_CHECK "; if (type & SCE_HTTPS_FLAG_NOT_BEFORE_CHECK) out += "SCE_HTTPS_FLAG_NOT_BEFORE_CHECK "; if (type & SCE_HTTPS_FLAG_KNOWN_CA_CHECK) out += "SCE_HTTPS_FLAG_KNOWN_CA_CHECK"; if (out.empty()) return std::to_string(type); return out; } EXPORT(int, sceHttpAbortRequest) { TRACY_FUNC(sceHttpAbortRequest); return UNIMPLEMENTED(); } EXPORT(int, sceHttpAbortRequestForce) { TRACY_FUNC(sceHttpAbortRequestForce); return UNIMPLEMENTED(); } EXPORT(int, sceHttpAbortWaitRequest) { TRACY_FUNC(sceHttpAbortWaitRequest); return UNIMPLEMENTED(); } EXPORT(int, sceHttpAddCookie) { TRACY_FUNC(sceHttpAddCookie); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpAddRequestHeader, SceInt reqId, const char *name, const char *value, SceHttpAddHeaderMode mode) { TRACY_FUNC(sceHttpAddRequestHeader, reqId, name, value, mode); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (mode != SCE_HTTP_HEADER_OVERWRITE && mode != SCE_HTTP_HEADER_ADD) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); if (mode == SCE_HTTP_HEADER_OVERWRITE && !name) return RET_ERROR(SCE_HTTP_ERROR_NOT_FOUND); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); if (!name || !value) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); auto &req = emuenv.http.requests.find(reqId)->second; if (mode == SCE_HTTP_HEADER_OVERWRITE) { auto foundIt = req.headers.find(name); if (foundIt != req.headers.end()) { // Entry already exists foundIt->second = std::string(value); } else { // entry doesn't exists, we can insert it req.headers.insert({ name, value }); } } else if (mode == SCE_HTTP_HEADER_ADD) { if (req.headers.contains(name)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); req.headers.insert({ name, value }); } else return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); return 0; } EXPORT(int, sceHttpAddRequestHeaderRaw) { TRACY_FUNC(sceHttpAddRequestHeaderRaw); return UNIMPLEMENTED(); } EXPORT(int, sceHttpAuthCacheFlush) { TRACY_FUNC(sceHttpAuthCacheFlush); return UNIMPLEMENTED(); } EXPORT(int, sceHttpCookieExport) { TRACY_FUNC(sceHttpCookieExport); return UNIMPLEMENTED(); } EXPORT(int, sceHttpCookieFlush) { TRACY_FUNC(sceHttpCookieFlush); return UNIMPLEMENTED(); } EXPORT(int, sceHttpCookieImport) { TRACY_FUNC(sceHttpCookieImport); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpCreateConnectionWithURL, SceInt tmplId, const char *url, SceBool enableKeepalive) { TRACY_FUNC(sceHttpCreateConnectionWithURL, tmplId, url, enableKeepalive); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.templates.contains(tmplId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); if (!url) return RET_ERROR(SCE_HTTP_ERROR_INVALID_URL); auto urlStr = std::string(url); auto tmpl = emuenv.http.templates.find(tmplId); int connId = emuenv.http.next_conn; emuenv.http.next_conn++; net_utils::parsedUrl parsed; auto parseRet = net_utils::parse_url(url, parsed); if (parseRet != 0) { switch (parseRet) { case SCE_HTTP_ERROR_UNKNOWN_SCHEME: { LOG_WARN("SCHEME IS: {}", parsed.scheme); return RET_ERROR(SCE_HTTP_ERROR_UNKNOWN_SCHEME); } case SCE_HTTP_ERROR_OUT_OF_SIZE: return RET_ERROR(SCE_HTTP_ERROR_OUT_OF_SIZE); default: LOG_WARN("Returning missing case of parse_url {}", (int)parseRet); assert(false); return parseRet; } } if (parsed.invalid) return RET_ERROR(SCE_HTTP_ERROR_INVALID_URL); bool isSecure = urlStr[4] == 's'; // Check if scheme is https // https:// // 012345 // ^Check this one std::string port; // 65535\0 // if URL doesn't have port, use protocol default if (parsed.port.empty()) port = isSecure ? "443" : "80"; else port = parsed.port; // If fifth character is an s (meaning https) use 443, else 80 int sockfd = socket(AF_INET, SOCK_STREAM, 0); assert(sockfd); if (sockfd < 0) { LOG_ERROR("ERROR opening socket"); return RET_ERROR(SCE_HTTP_ERROR_UNKNOWN); } const addrinfo hints = { AI_PASSIVE, /* For wildcard IP address */ AF_UNSPEC, /* Allow IPv4 or IPv6 */ SOCK_DGRAM, /* Datagram socket */ 0, /* Any protocol */ }; addrinfo *result = { 0 }; const ThreadStatePtr thread = lock_and_find(thread_id, emuenv.kernel.threads, emuenv.kernel.mutex); auto ret = getaddrinfo(parsed.hostname.c_str(), port.c_str(), &hints, &result); if (ret < 0) { if (!emuenv.cfg.http_enable) { LOG_WARN("getaddrinfo failed, but http is disabled, asume we still got it"); // Even if we didnt get the ip, we should send the ip obtained callback for (auto &callback : emuenv.netctl.callbacks) { if (callback.pc != 0) { thread->run_callback(callback.pc, { SCE_NET_CTL_EVENT_TYPE_IPOBTAINED, callback.arg }); } } // Need to push the connection here so the id exists when "sending" the request emuenv.http.connections.emplace(connId, SceConnection{ tmplId, urlStr, enableKeepalive, isSecure, sockfd }); return connId; } LOG_ERROR("getaddrinfo({},{},...) = {}", url, port, ret); return RET_ERROR(SCE_HTTP_ERROR_RESOLVER_ENODNS); } // We got the ip, send the IPOPBTAINED callback event for (auto &callback : emuenv.netctl.callbacks) { if (callback.pc != 0) { thread->run_callback(callback.pc, { SCE_NET_CTL_EVENT_TYPE_IPOBTAINED, callback.arg }); } } if (!emuenv.cfg.http_enable) { // Need to push the connection here so the id exists when "sending" the request emuenv.http.connections.emplace(connId, SceConnection{ tmplId, urlStr, enableKeepalive, isSecure, sockfd }); return connId; } ret = connect(sockfd, result->ai_addr, result->ai_addrlen); if (ret < 0) { LOG_ERROR("connect({},...) = {}, errno={}({})", sockfd, ret, errno, strerror(errno)); return RET_ERROR(SCE_HTTP_ERROR_RESOLVER_ENOHOST); } LOG_TRACE("Connected to {}", url); if (isSecure) { if (!emuenv.http.sslInited) { LOG_ERROR("SSL not inited on secure connection"); return RET_ERROR(SCE_HTTP_ERROR_SSL); } SSL_set_fd((SSL *)tmpl->second.ssl, sockfd); // This is needed as some servers are using handshake protocols older than the person writing this code SSL_set_security_level((SSL *)tmpl->second.ssl, 0); int err = SSL_connect((SSL *)tmpl->second.ssl); if (err != 1) { int sslErr = SSL_get_error((SSL *)tmpl->second.ssl, err); LOG_ERROR("SSL_connect(...) = {}, SSLERR = {}", err, sslErr); if (sslErr == SSL_ERROR_SSL) return RET_ERROR(SCE_HTTP_ERROR_SSL); } long verify_flag = SSL_get_verify_result((SSL *)tmpl->second.ssl); if (verify_flag != X509_V_OK && verify_flag != X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) LOG_ERROR("Certificate verification error ({}) but continuing...\n", (int)verify_flag); } emuenv.http.connections.emplace(connId, SceConnection{ tmplId, urlStr, enableKeepalive, isSecure, sockfd }); return connId; } EXPORT(SceInt, sceHttpCreateConnection, SceInt tmplId, const char *hostname, const char *scheme, SceUShort16 port, SceBool enableKeepalive) { TRACY_FUNC(sceHttpCreateConnection, tmplId, hostname, scheme, port, enableKeepalive); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.templates.contains(tmplId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); if (!scheme) return RET_ERROR(SCE_HTTP_ERROR_UNKNOWN_SCHEME); const auto schemeStr = std::string_view(scheme); if (schemeStr != "http" && schemeStr != "https") { LOG_WARN("SCHEME IS: {}", scheme); return RET_ERROR(SCE_HTTP_ERROR_UNKNOWN_SCHEME); } if (!hostname) return RET_ERROR(SCE_HTTP_ERROR_INVALID_URL); if (port == 0) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); std::string url = std::string(scheme) + "://" + std::string(hostname) + ":" + std::to_string(port); return CALL_EXPORT(sceHttpCreateConnectionWithURL, tmplId, url.c_str(), enableKeepalive); } EXPORT(int, sceHttpCreateEpoll) { TRACY_FUNC(sceHttpCreateEpoll); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpCreateRequestWithURL, SceInt connId, SceHttpMethods method, const char *url, SceULong64 contentLength) { TRACY_FUNC(sceHttpCreateRequestWithURL, connId, method, url, contentLength); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.connections.contains(connId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); if (method >= SCE_HTTP_METHOD_INVALID || method < 0) return RET_ERROR(SCE_HTTP_ERROR_UNKNOWN_METHOD); if (!url) return RET_ERROR(SCE_HTTP_ERROR_INVALID_URL); auto urlStr = std::string(url); auto conn = emuenv.http.connections.find(connId); auto tmpl = emuenv.http.templates.find(conn->second.tmplId); // Check if the URL of the request and connection are the same if (conn->second.url != urlStr) LOG_WARN("URL != Connection URL"); int reqId = emuenv.http.next_req; emuenv.http.next_req++; std::string httpVer = tmpl->second.httpVersion == SCE_HTTP_VERSION_1_0 ? "HTTP/1.0" : "HTTP/1.1"; net_utils::parsedUrl parsed; auto parseRet = net_utils::parse_url(urlStr, parsed); if (parseRet != 0) { switch (parseRet) { case SCE_HTTP_ERROR_UNKNOWN_SCHEME: { LOG_WARN("SCHEME IS: {}", parsed.scheme); return RET_ERROR(SCE_HTTP_ERROR_UNKNOWN_SCHEME); } case SCE_HTTP_ERROR_OUT_OF_SIZE: return RET_ERROR(SCE_HTTP_ERROR_OUT_OF_SIZE); default: LOG_WARN("Returning missing case of parse_url {}", (int)parseRet); assert(false); return parseRet; } } if (parsed.invalid) return RET_ERROR(SCE_HTTP_ERROR_INVALID_URL); if (parsed.path.empty()) parsed.path = "/"; std::string resourcePath = parsed.path + parsed.query + parsed.fragment; SceRequest req; req.connId = connId; req.method = method; req.url = urlStr; req.contentLength = contentLength; req.headers.insert({ "Host", parsed.hostname }); req.headers.insert({ "User-Agent", tmpl->second.userAgent }); if (tmpl->second.httpVersion == SCE_HTTP_VERSION_1_1 && conn->second.keepAlive) req.headers.insert({ "Connection", "Keep-Alive" }); std::string methodStr; switch (method) { case SCE_HTTP_METHOD_GET: methodStr = "GET"; break; case SCE_HTTP_METHOD_POST: methodStr = "POST"; break; case SCE_HTTP_METHOD_HEAD: methodStr = "HEAD"; break; case SCE_HTTP_METHOD_OPTIONS: methodStr = "OPTIONS"; break; case SCE_HTTP_METHOD_PUT: methodStr = "PUT"; break; case SCE_HTTP_METHOD_DELETE: methodStr = "DELETE"; break; case SCE_HTTP_METHOD_TRACE: methodStr = "TRACE"; break; case SCE_HTTP_METHOD_CONNECT: methodStr = "CONNECT"; break; default: return RET_ERROR(SCE_HTTP_ERROR_UNKNOWN_METHOD); } req.requestLine = methodStr + " " + resourcePath + " " + httpVer; emuenv.http.requests.emplace(reqId, req); return reqId; } EXPORT(SceInt, sceHttpCreateRequest, SceInt connId, SceHttpMethods method, const char *path, SceULong64 contentLength) { TRACY_FUNC(sceHttpCreateRequest, connId, method, path, contentLength); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.connections.contains(connId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); if (method >= SCE_HTTP_METHOD_INVALID || method < 0) return RET_ERROR(SCE_HTTP_ERROR_UNKNOWN_METHOD); if (!path) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); auto conn = emuenv.http.connections.find(connId); return CALL_EXPORT(sceHttpCreateRequestWithURL, connId, method, conn->second.url.c_str(), contentLength); } EXPORT(SceInt, sceHttpCreateRequest2, SceInt connId, const char *method, const char *path, SceULong64 contentLength) { TRACY_FUNC(sceHttpCreateRequest2, connId, method, path, contentLength); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.connections.contains(connId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); if (!path) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); auto conn = emuenv.http.connections.find(connId); auto intMethod = (SceHttpMethods)net_utils::char_method_to_int(method); // Even if it returns error (-1), it will get handled in the call return CALL_EXPORT(sceHttpCreateRequestWithURL, connId, intMethod, conn->second.url.c_str(), contentLength); } EXPORT(SceInt, sceHttpCreateRequestWithURL2, SceInt connId, const char *method, const char *path, SceULong64 contentLength) { TRACY_FUNC(sceHttpCreateRequestWithURL2, connId, method, path, contentLength); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.connections.contains(connId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); if (!path) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); auto intMethod = (SceHttpMethods)net_utils::char_method_to_int(method); // Even if it returns error (-1), it will get handled in the call return CALL_EXPORT(sceHttpCreateRequestWithURL, connId, intMethod, path, contentLength); } EXPORT(SceInt, sceHttpCreateTemplate, const char *userAgent, SceHttpVersion httpVer, SceBool autoProxyConf) { TRACY_FUNC(sceHttpCreateTemplate, userAgent, httpVer, autoProxyConf); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!userAgent) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); if (httpVer != SceHttpVersion::SCE_HTTP_VERSION_1_0 && httpVer != SceHttpVersion::SCE_HTTP_VERSION_1_1) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VERSION); SceInt tmplId = emuenv.http.next_temp; emuenv.http.next_temp++; void *ssl_ctx = nullptr; if (emuenv.http.sslInited) ssl_ctx = emuenv.http.ssl_ctx; else ssl_ctx = SSL_CTX_new(TLS_method()); SSL_CTX_set_mode((SSL_CTX *)ssl_ctx, SSL_MODE_AUTO_RETRY); auto ssl = SSL_new((SSL_CTX *)ssl_ctx); emuenv.http.templates.emplace(tmplId, SceTemplate{ std::string(userAgent), httpVer, autoProxyConf, ssl }); return tmplId; } EXPORT(SceInt, sceHttpDeleteConnection, SceInt connId) { TRACY_FUNC(sceHttpDeleteConnection, connId); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); auto connIt = emuenv.http.connections.find(connId); if (connIt == emuenv.http.connections.end()) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); #ifdef _WIN32 closesocket(connIt->second.sockfd); #else close(connIt->second.sockfd); #endif // _WIN32 emuenv.http.connections.erase(connIt); return 0; } EXPORT(SceInt, sceHttpDeleteRequest, SceInt reqId) { TRACY_FUNC(sceHttpDeleteRequest, reqId); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); auto it = emuenv.http.requests.find(reqId); if (it == emuenv.http.requests.end()) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); delete[] it->second.res.responseRaw; for (auto pointer : it->second.guestPointers) { free(emuenv.mem, pointer.address()); } emuenv.http.requests.erase(it); return 0; } EXPORT(SceInt, sceHttpDeleteTemplate, SceInt tmplId) { TRACY_FUNC(sceHttpDeleteTemplate, tmplId); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.templates.contains(tmplId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto it = emuenv.http.templates.find(tmplId); SSL_free((SSL *)it->second.ssl); emuenv.http.templates.erase(it); return 0; } EXPORT(int, sceHttpDestroyEpoll) { TRACY_FUNC(sceHttpDestroyEpoll); return UNIMPLEMENTED(); } EXPORT(int, sceHttpGetAcceptEncodingGZIPEnabled) { TRACY_FUNC(sceHttpGetAcceptEncodingGZIPEnabled); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpGetAllResponseHeaders, SceInt reqId, Ptr *header, SceSize *headerSize) { TRACY_FUNC(sceHttpGetAllResponseHeaders, reqId, header, headerSize); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto req = emuenv.http.requests.find(reqId); auto headers = net_utils::constructHeaders(req->second.res.headers); // is alloc name ok? auto h = Ptr(alloc(emuenv.mem, headers.length() + 1, "header")); // Allocate on guest mem memcpy(h.get(emuenv.mem), headers.data(), headers.length() + 1); // Put header data on guest mem req->second.guestPointers.emplace_back(h); // Save the pointer to free it later *header = h; // make header point to the guest address where headers are located *headerSize = headers.length() + 1; return 0; } EXPORT(int, sceHttpGetAuthEnabled) { TRACY_FUNC(sceHttpGetAuthEnabled); return UNIMPLEMENTED(); } EXPORT(int, sceHttpGetAutoRedirect) { TRACY_FUNC(sceHttpGetAutoRedirect); return UNIMPLEMENTED(); } EXPORT(int, sceHttpGetCookie) { TRACY_FUNC(sceHttpGetCookie); return UNIMPLEMENTED(); } EXPORT(int, sceHttpGetCookieEnabled) { TRACY_FUNC(sceHttpGetCookieEnabled); return UNIMPLEMENTED(); } EXPORT(int, sceHttpGetCookieStats) { TRACY_FUNC(sceHttpGetCookieStats); return UNIMPLEMENTED(); } EXPORT(int, sceHttpGetEpoll) { TRACY_FUNC(sceHttpGetEpoll); return UNIMPLEMENTED(); } EXPORT(int, sceHttpGetEpollId) { TRACY_FUNC(sceHttpGetEpollId); return UNIMPLEMENTED(); } EXPORT(int, sceHttpGetIcmOption) { TRACY_FUNC(sceHttpGetIcmOption); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpGetLastErrno, SceInt reqId, SceInt *errNum) { TRACY_FUNC(sceHttpGetLastErrno, reqId, errNum); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); *errNum = errno; return 0; } EXPORT(SceInt, sceHttpGetMemoryPoolStats, SceHttpMemoryPoolStats *currentStat) { TRACY_FUNC(sceHttpGetMemoryPoolStats, currentStat); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!currentStat) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); return UNIMPLEMENTED(); } EXPORT(int, sceHttpGetNonblock) { TRACY_FUNC(sceHttpGetNonblock); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpGetResponseContentLength, SceInt reqId, SceULong64 *contentLength) { TRACY_FUNC(sceHttpGetResponseContentLength, reqId, contentLength); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!contentLength) return RET_ERROR(SCE_HTTP_ERROR_NO_CONTENT_LENGTH); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto req = emuenv.http.requests.find(reqId); if (!emuenv.cfg.http_enable) { *contentLength = req->second.res.contentLength; return 0; } auto length_it = req->second.res.headers.find("Content-Length"); if (length_it == req->second.res.headers.end()) return RET_ERROR(SCE_HTTP_ERROR_NO_CONTENT_LENGTH); SceULong64 length = string_utils::stoi_def(length_it->second); *contentLength = length; req->second.res.contentLength = length; return 0; } EXPORT(SceInt, sceHttpGetStatusCode, SceInt reqId, SceInt *statusCode) { TRACY_FUNC(sceHttpGetStatusCode, reqId, statusCode); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto req = emuenv.http.requests.find(reqId); *statusCode = req->second.res.statusCode; return 0; } EXPORT(SceInt, sceHttpInit, SceSize poolSize) { TRACY_FUNC(sceHttpInit, poolSize); if (emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_ALREADY_INITED); if (poolSize == 0) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); STUBBED("ignore poolSize"); if (emuenv.http.sslInited) emuenv.http.ssl_ctx = SSL_CTX_new(TLS_method()); emuenv.http.inited = true; return 0; } EXPORT(SceInt, sceHttpParseResponseHeader, Ptr headers, SceSize headersLen, const char *fieldStr, Ptr *fieldValue, SceSize *valueLen) { TRACY_FUNC(sceHttpParseResponseHeader, headers, headersLen, fieldStr, fieldValue, valueLen); if (!headers) return RET_ERROR(SCE_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE); if (!fieldStr || !fieldValue || valueLen == 0) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); if (headersLen == 0) return RET_ERROR(SCE_HTTP_ERROR_PARSE_HTTP_NOT_FOUND); *valueLen = 0; // reset to 0 to check after std::string headerStr = std::string(headers.get(emuenv.mem)); HeadersMapType parsedHeaders; if (!net_utils::parseHeaders(headerStr, parsedHeaders)) return RET_ERROR(SCE_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE); auto foundIt = parsedHeaders.find(fieldStr); if (foundIt == parsedHeaders.end()) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); // is alloc name ok? auto h = Ptr(alloc(emuenv.mem, foundIt->second.length() + 1, "fieldValue")); // Allocate on guest mem memcpy(h.get(emuenv.mem), foundIt->second.data(), foundIt->second.length() + 1); // Put header data on guest mem emuenv.http.guestPointers.emplace_back(h); // Save the pointer to free it later *fieldValue = h; // make header point to the guest address where headers are located *valueLen = foundIt->second.length() + 1; return 0; } EXPORT(SceInt, sceHttpParseStatusLine, const char *statusLine, SceSize lineLen, SceInt *httpMajorVer, SceInt *httpMinorVer, SceInt *responseCode, Ptr *reasonPhrase, SceSize *phraseLen) { TRACY_FUNC(sceHttpParseStatusLine, statusLine, lineLen, httpMajorVer, httpMinorVer, responseCode, reasonPhrase, phraseLen); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!statusLine) return RET_ERROR(SCE_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE); if (httpMajorVer == 0 || httpMinorVer == 0 || responseCode == 0 || reasonPhrase == 0 || phraseLen == 0) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); *httpMajorVer = 0; *httpMinorVer = 0; if (lineLen < 8) return RET_ERROR(SCE_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE); STUBBED("Ignore lineLen"); // TODO: test auto line = std::string(statusLine); auto cleanLine = line.substr(0, line.find("\r\n")); // even if there is no \r\n, the result will still be the whole string std::string version; int code; std::string reason; if (!net_utils::parseStatusLine(cleanLine, version, code, reason)) return RET_ERROR(SCE_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE); *httpMajorVer = string_utils::stoi_def(version.substr(0, version.find('.'))); // we know this wont fail because parseStatusLine returned true :) if (version.find('.') != std::string::npos) { auto minorVer = version.substr(version.find('.') + 1); *httpMinorVer = string_utils::stoi_def(minorVer); } else { *httpMinorVer = 0; } auto statusCodeLine = cleanLine.substr(cleanLine.find(' ') + 1, 3); *responseCode = code; auto h = Ptr(alloc(emuenv.mem, sizeof(char), "reasonPhrase")); memcpy(h.get(emuenv.mem), reason.data(), reason.length() + 1); emuenv.http.guestPointers.emplace_back(h); *reasonPhrase = h; *phraseLen = reason.length() + 1; return 0; } EXPORT(SceInt, sceHttpReadData, SceInt reqId, void *data, SceSize size) { TRACY_FUNC(sceHttpReadData, reqId, data, size); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto req = emuenv.http.requests.find(reqId); // These methods have no body if (req->second.method == SCE_HTTP_METHOD_HEAD || req->second.method == SCE_HTTP_METHOD_OPTIONS) return 0; // If the game wants to read more than whats available, change the read ammount to what is available if (size > (req->second.res.contentLength - req->second.res.responseRead)) { size = req->second.res.contentLength - req->second.res.responseRead; } if (req->second.res.responseRead == req->second.res.contentLength) { // If we already have read all the response. return 0; } if (!emuenv.cfg.http_enable) { memset(data, 0, size); LOG_WARN("READ DATA FILLED WITH 0"); } else { memcpy(data, req->second.res.body + req->second.res.responseRead, size); } req->second.res.responseRead += size; return size; } EXPORT(int, sceHttpRedirectCacheFlush) { TRACY_FUNC(sceHttpRedirectCacheFlush); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpRemoveRequestHeader, SceInt reqId, const char *name) { TRACY_FUNC(sceHttpRemoveRequestHeader, reqId, name); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto req = emuenv.http.requests.find(reqId); if (req->second.headers.contains(name)) req->second.headers.erase(name); return 0; } EXPORT(SceInt, sceHttpRequestGetAllHeaders, SceInt reqId, Ptr *header, SceSize *headerSize) { TRACY_FUNC(sceHttpRequestGetAllHeaders, reqId, header, headerSize); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto req = emuenv.http.requests.find(reqId); auto headers = net_utils::constructHeaders(req->second.headers); auto h = Ptr(alloc(emuenv.mem, sizeof(char), "headers")); memcpy(h.get(emuenv.mem), headers.data(), headers.length() + 1); req->second.guestPointers.emplace_back(h); *header = h; *headerSize = headers.length() + 1; return 0; } EXPORT(SceInt, sceHttpSendRequest, SceInt reqId, const char *postData, SceSize size) { TRACY_FUNC(sceHttpSendRequest, reqId, postData, size); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto req = emuenv.http.requests.find(reqId); auto conn = emuenv.http.connections.find(req->second.connId); auto tmpl = emuenv.http.templates.find(conn->second.tmplId); if (!emuenv.cfg.http_enable) { req->second.res.statusCode = 200; req->second.res.reasonPhrase = "OK"; req->second.res.contentLength = 4096; STUBBED("statusCode = 200; reason = \"OK\"; contentLength = 4096"); return 0; } LOG_DEBUG("Sending {} request to {}", net_utils::int_method_to_char(req->second.method), req->second.url); // TODO: Also support file scheme, doesn't really require any connections, not sure how it handles headers and such if (req->second.method == SCE_HTTP_METHOD_TRACE || req->second.method == SCE_HTTP_METHOD_CONNECT) { LOG_WARN("Unimplemented method {}, report to devs", req->second.method); return 0; } /* TODO: TRACE CONNECT */ if (req->second.method == SCE_HTTP_METHOD_POST || req->second.method == SCE_HTTP_METHOD_PUT) { if (req->second.headers.contains("Content-Length")) { // There is a content length header, probably by the game, use it auto contHeader = req->second.headers.find("Content-Length"); SceSize contLen = string_utils::stoi_def(contHeader->second); // Its ok to have the content length be less or equal than size, // but not the other way around. It would be sending undefined data leading to undefined behavior if (contLen > size) LOG_WARN("POST/PUT request Header: ContentLength > size."); // Set size to contLen to not send extra stuff the server will ignore size = contLen; } else { // No Content-Length header // if size and predefined aren't equal, we will use predefined if (req->second.contentLength != size) { LOG_WARN("POST/PUT request Header: predefined != size."); size = req->second.contentLength; } auto contLen = std::to_string(size); auto ret = CALL_EXPORT(sceHttpAddRequestHeader, reqId, "Content-Length", contLen.c_str(), SCE_HTTP_HEADER_ADD); if (ret < 0) { LOG_WARN("huh?"); assert(false); } } } auto headers = net_utils::constructHeaders(req->second.headers); req->second.message = req->second.requestLine + "\r\n" + headers + "\r\n"; int msgLength = req->second.message.length(); int reqBytesSent = 0; do { int bytes = 0; if (conn->second.isSecure) bytes = SSL_write((SSL *)tmpl->second.ssl, req->second.message.c_str() + reqBytesSent, msgLength - reqBytesSent); else bytes = write(conn->second.sockfd, req->second.message.c_str() + reqBytesSent, msgLength - reqBytesSent); if (bytes < 0) { LOG_ERROR("ERROR writing GET message to socket"); assert(false); return RET_ERROR(SCE_HTTP_ERROR_NETWORK); } LOG_TRACE("Sent {} bytes to {}", bytes, req->second.url); if (bytes == 0) break; reqBytesSent += bytes; } while (reqBytesSent < msgLength); if (req->second.method == SCE_HTTP_METHOD_POST || req->second.method == SCE_HTTP_METHOD_PUT) { // Once we send the request we need to send the actual data SceSize dataSent = 0; auto dataBytes = 0; do { if (conn->second.isSecure) dataBytes = SSL_write((SSL *)tmpl->second.ssl, postData + dataSent, size - dataSent); else dataBytes = write(conn->second.sockfd, postData + dataSent, size - dataSent); if (dataBytes < 0) { LOG_ERROR("ERROR writing POST data to socket"); assert(false); return RET_ERROR(SCE_HTTP_ERROR_NETWORK); } LOG_TRACE("Sent {} data bytes to {}", dataBytes, req->second.url); if (dataBytes == 0) break; dataSent += dataBytes; } while (dataSent < size); } // Make sockets non blocking only for the response // as we can run out of data to read so it blocks like a whole minute if (!net_utils::socketSetBlocking(conn->second.sockfd, false)) { LOG_WARN("Failed to change blocking, socket={}, blocking={}", conn->second.sockfd, false); assert(false); } /* receive the response */ int attempts = 1; auto resHeaders = new char[emuenv.http.defaultResponseHeaderSize](); auto resHeadersMaxSize = emuenv.http.defaultResponseHeaderSize; int totalReceived = 0; do { int bytes = 0; if (conn->second.isSecure) bytes = SSL_read((SSL *)tmpl->second.ssl, resHeaders + totalReceived, resHeadersMaxSize - totalReceived); else bytes = read(conn->second.sockfd, resHeaders + totalReceived, resHeadersMaxSize - totalReceived); if (bytes < 0) { if (bytes == -1) { if (errno == EWOULDBLOCK) { if (attempts > emuenv.cfg.http_read_end_attempts) break; // we can assume there is no more data to read LOG_TRACE("No data available. Sleep for {}. Attempt {}", emuenv.cfg.http_read_end_sleep_ms, attempts); std::this_thread::sleep_for(std::chrono::milliseconds(emuenv.cfg.http_read_end_sleep_ms)); attempts++; continue; } else { LOG_ERROR("ERROR reading GET response headers"); assert(false); delete[] resHeaders; return RET_ERROR(SCE_HTTP_ERROR_NETWORK); } } } LOG_TRACE("Received {} bytes from {}", bytes, req->second.url); if (bytes == 0) { if (strcmp(resHeaders, "") == 0) { if (attempts > emuenv.cfg.http_timeout_attempts) break; // Give up LOG_TRACE("Response is null. Sleep for {}. Attempt {}", emuenv.cfg.http_timeout_sleep_ms, attempts); std::this_thread::sleep_for(std::chrono::milliseconds(emuenv.cfg.http_timeout_sleep_ms)); attempts++; continue; } break; } totalReceived += bytes; } while (std::string_view(resHeaders).find("\r\n\r\n") == std::string::npos || totalReceived == resHeadersMaxSize); // receive headers until we start receiving body if (totalReceived != resHeadersMaxSize && std::string_view(resHeaders).find("\r\n\r\n") == std::string::npos) { delete[] resHeaders; return RET_ERROR(SCE_HTTP_ERROR_TIMEOUT); } // Headers are too big if (totalReceived == resHeadersMaxSize && std::string_view(resHeaders).find("\r\n\r\n") == std::string::npos) { delete[] resHeaders; return RET_ERROR(SCE_HTTP_ERROR_TOO_LARGE_RESPONSE_HEADER); } const auto resHeadersStr = std::string(resHeaders); const auto resHeadersOnly = resHeadersStr.substr(0, resHeadersStr.find("\r\n\r\n")); if (!net_utils::parseResponse(resHeadersOnly, req->second.res)) { delete[] resHeaders; return RET_ERROR(SCE_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE); } // TODO: does a HEAD/OPTIONS request need content-length to exist? if (req->second.res.headers.find("content-length") == req->second.headers.end()) { delete[] resHeaders; return RET_ERROR(SCE_HTTP_ERROR_NO_CONTENT_LENGTH); } LOG_TRACE("Request replied with status code {}", req->second.res.statusCode); // Now we get the body or the rest of the body attempts = 1; // Reset attempts const int responseLength = resHeadersStr.find("\r\n\r\n") + strlen("\r\n\r\n") + req->second.res.contentLength; if (req->second.method == SCE_HTTP_METHOD_HEAD || req->second.method == SCE_HTTP_METHOD_OPTIONS) // even if we have content-length, there will be no body const int responseLength = resHeadersStr.find("\r\n\r\n") + strlen("\r\n\r\n"); // This is the entire response, including headers and everything auto reqResponse = new uint8_t[responseLength](); memcpy(reqResponse, resHeaders, std::min(emuenv.http.defaultResponseHeaderSize, responseLength)); delete[] resHeaders; int remainingToRead = responseLength - totalReceived; while (remainingToRead != 0) { int bytes = 0; if (conn->second.isSecure) bytes = SSL_read((SSL *)tmpl->second.ssl, reqResponse + totalReceived, remainingToRead); else bytes = read(conn->second.sockfd, reqResponse + totalReceived, remainingToRead); if (bytes < 0) { if (bytes == -1) { if (errno == EWOULDBLOCK) { if (attempts > emuenv.cfg.http_read_end_attempts) break; // we can assume there is no more data to read LOG_TRACE("No data available. Sleep for {}. Attempt {}", emuenv.cfg.http_read_end_sleep_ms, attempts); std::this_thread::sleep_for(std::chrono::milliseconds(emuenv.cfg.http_read_end_sleep_ms)); attempts++; continue; } else { LOG_ERROR("ERROR reading GET response"); assert(false); delete[] reqResponse; return RET_ERROR(SCE_HTTP_ERROR_NETWORK); } } } LOG_TRACE("Received {} bytes from {}", bytes, req->second.url); if (bytes == 0) { if (attempts > emuenv.cfg.http_timeout_attempts) break; // Give up LOG_TRACE("Response is null. Sleep for {}. Attempt {}", emuenv.cfg.http_timeout_sleep_ms, attempts); std::this_thread::sleep_for(std::chrono::milliseconds(emuenv.cfg.http_timeout_sleep_ms)); attempts++; continue; } totalReceived += bytes; remainingToRead -= bytes; } if (!net_utils::socketSetBlocking(conn->second.sockfd, true)) { LOG_WARN("Failed to change blocking, socket={}, blocking={}", conn->second.sockfd, true); assert(false); } if (remainingToRead != 0) { LOG_WARN("Could not read entire body length"); delete[] reqResponse; return RET_ERROR(SCE_HTTP_ERROR_TIMEOUT); } if (*reqResponse == 0) { LOG_ERROR("Received empty GET response. Probably due to unknown protocol"); assert(false); delete[] reqResponse; return RET_ERROR(SCE_HTTP_ERROR_BAD_RESPONSE); } req->second.res.responseRaw = reqResponse; req->second.res.body = reqResponse + resHeadersOnly.length() + strlen("\r\n\r\n"); LOG_TRACE("Request finished nicely"); return 0; } EXPORT(int, sceHttpSetAcceptEncodingGZIPEnabled) { TRACY_FUNC(sceHttpSetAcceptEncodingGZIPEnabled); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetAuthEnabled) { TRACY_FUNC(sceHttpSetAuthEnabled); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetAuthInfoCallback) { TRACY_FUNC(sceHttpSetAuthInfoCallback); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetAutoRedirect) { TRACY_FUNC(sceHttpSetAutoRedirect); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetConnectTimeOut) { TRACY_FUNC(sceHttpSetConnectTimeOut); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetCookieEnabled) { TRACY_FUNC(sceHttpSetCookieEnabled); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetCookieMaxNum) { TRACY_FUNC(sceHttpSetCookieMaxNum); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetCookieMaxNumPerDomain) { TRACY_FUNC(sceHttpSetCookieMaxNumPerDomain); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetCookieMaxSize) { TRACY_FUNC(sceHttpSetCookieMaxSize); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetCookieRecvCallback) { TRACY_FUNC(sceHttpSetCookieRecvCallback); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetCookieSendCallback) { TRACY_FUNC(sceHttpSetCookieSendCallback); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetCookieTotalMaxSize) { TRACY_FUNC(sceHttpSetCookieTotalMaxSize); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetDefaultAcceptEncodingGZIPEnabled) { TRACY_FUNC(sceHttpSetDefaultAcceptEncodingGZIPEnabled); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetEpoll) { TRACY_FUNC(sceHttpSetEpoll); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetEpollId) { TRACY_FUNC(sceHttpSetEpollId); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetIcmOption) { TRACY_FUNC(sceHttpSetIcmOption); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetInflateGZIPEnabled) { TRACY_FUNC(sceHttpSetInflateGZIPEnabled); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetNonblock) { TRACY_FUNC(sceHttpSetNonblock); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetRecvTimeOut) { TRACY_FUNC(sceHttpSetRecvTimeOut); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetRedirectCallback) { TRACY_FUNC(sceHttpSetRedirectCallback); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpSetRequestContentLength, SceInt reqId, SceULong64 contentLength) { TRACY_FUNC(sceHttpSetRequestContentLength, reqId, contentLength); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto req = emuenv.http.requests.find(reqId); req->second.contentLength = contentLength; return 0; } EXPORT(int, sceHttpSetResolveRetry) { TRACY_FUNC(sceHttpSetResolveRetry); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSetResolveTimeOut) { TRACY_FUNC(sceHttpSetResolveTimeOut); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpSetResponseHeaderMaxSize, SceInt reqId, SceSize headerSize) { TRACY_FUNC(sceHttpSetResponseHeaderMaxSize, reqId, headerSize); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.requests.contains(reqId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); if (headerSize > SCE_HTTP_DEFAULT_RESPONSE_HEADER_MAX) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); // TODO: set this per http request emuenv.http.defaultResponseHeaderSize = headerSize; return 0; } EXPORT(int, sceHttpSetSendTimeOut) { TRACY_FUNC(sceHttpSetSendTimeOut); return UNIMPLEMENTED(); } EXPORT(int, sceHttpSslIsCtxCreated) { TRACY_FUNC(sceHttpSslIsCtxCreated); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpTerm) { TRACY_FUNC(sceHttpTerm); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); // clear everything for (auto &tmpl : emuenv.http.templates) { CALL_EXPORT(sceHttpDeleteTemplate, tmpl.first); } emuenv.http.templates.clear(); for (auto &conn : emuenv.http.connections) { CALL_EXPORT(sceHttpDeleteConnection, conn.first); } emuenv.http.connections.clear(); for (auto &req : emuenv.http.requests) { CALL_EXPORT(sceHttpDeleteRequest, req.first); } emuenv.http.requests.clear(); for (auto &pointer : emuenv.http.guestPointers) { free(emuenv.mem, pointer.address()); } return 0; } EXPORT(int, sceHttpUnsetEpoll) { TRACY_FUNC(sceHttpUnsetEpoll); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpUriBuild, char *out, SceSize *require, SceSize prepare, const SceHttpUriElement *srcElement, SceUInt option) { TRACY_FUNC(sceHttpUriBuild, out, require, prepare, srcElement, option); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!out || !srcElement) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); // TODO: need example from game return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpUriEscape, char *out, SceSize *require, SceSize prepare, const char *in) { TRACY_FUNC(sceHttpUriEscape, out, require, prepare, in); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!out || !in) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpUriMerge, char *mergedUrl, const char *url, const char *relativeUrl, SceSize *require, SceSize prepare, SceUInt option) { TRACY_FUNC(sceHttpUriMerge, mergedUrl, url, relativeUrl, require, prepare, option); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!mergedUrl || !relativeUrl) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); if (!url) return RET_ERROR(SCE_HTTP_ERROR_INVALID_URL); // TODO: need example from game return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpUriParse, SceHttpUriElement *out, const char *srcUrl, Ptr pool, SceSize *require, SceSize prepare) { TRACY_FUNC(sceHttpUriParse, out, srcUrl, pool, require, prepare); if (!srcUrl) return RET_ERROR(SCE_HTTP_ERROR_INVALID_URL); if ((!pool || !out) && !require) { return SCE_HTTP_ERROR_INVALID_VALUE; } net_utils::parsedUrl parsed; auto parseRet = net_utils::parse_url(srcUrl, parsed); if (parseRet != 0) { switch (parseRet) { case SCE_HTTP_ERROR_UNKNOWN_SCHEME: { LOG_WARN("SCHEME IS: {}", parsed.scheme); return RET_ERROR(SCE_HTTP_ERROR_UNKNOWN_SCHEME); } case SCE_HTTP_ERROR_OUT_OF_SIZE: return RET_ERROR(SCE_HTTP_ERROR_OUT_OF_SIZE); default: LOG_WARN("Returning missing case of parse_url {}", (int)parseRet); assert(false); return parseRet; } } if (parsed.invalid) return RET_ERROR(SCE_HTTP_ERROR_INVALID_URL); if (parsed.port.empty()) parsed.port = "0"; // Threat 0 as invalid, even if it isn't const SceSize internal_require = parsed.scheme.size() + parsed.username.size() + parsed.password.size() + parsed.hostname.size() + parsed.path.size() + parsed.query.size() + parsed.fragment.size() + 7; if (require) { *require = internal_require; } if (prepare < internal_require) return RET_ERROR(SCE_HTTP_ERROR_OUT_OF_MEMORY); if (pool && out) { memset(out, 0, sizeof(*out)); auto pool_ptr = pool.address(); const auto set_str_value = [&](Ptr &field, const std::string &value) { field = Ptr(pool_ptr); strcpy(field.get(emuenv.mem), value.c_str()); pool_ptr += value.size() + 1; }; out->opaque = false; set_str_value(out->scheme, parsed.scheme); set_str_value(out->username, parsed.username); set_str_value(out->password, parsed.password); set_str_value(out->hostname, parsed.hostname); set_str_value(out->path, parsed.path); set_str_value(out->query, parsed.query); set_str_value(out->fragment, parsed.fragment); SceUShort16 port = string_utils::stoi_def(parsed.port); out->port = port; } return 0; } EXPORT(SceInt, sceHttpUriSweepPath, char *dst, const char *src, SceSize srcSize) { TRACY_FUNC(sceHttpUriSweepPath, dst, src, srcSize); if (srcSize == 0) return 0; if (!dst || !src) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); auto srcStr = std::string(src); unsigned int srcStrLen = srcSize - 1; if (srcStr[0] == '/') { auto lex = std::filesystem::path(src).lexically_normal(); const std::string lexStr = lex.string(); const char *realPath = lexStr.c_str(); strcpy(dst, realPath); } else { // Nicely copy the contents of src into dst memcpy(dst, src, srcStrLen); dst[srcSize - 1] = 0; } return 0; } EXPORT(int, sceHttpUriUnescape, char *out, SceSize *require, SceSize prepare, const char *in) { TRACY_FUNC(sceHttpUriUnescape, out, require, prepare, in); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!out || !in) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); return UNIMPLEMENTED(); } EXPORT(int, sceHttpWaitRequest) { TRACY_FUNC(sceHttpWaitRequest); return UNIMPLEMENTED(); } EXPORT(int, sceHttpWaitRequestCB) { TRACY_FUNC(sceHttpWaitRequestCB); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpsDisableOption, SceHttpsFlags sslFlags) { TRACY_FUNC(sceHttpsDisableOption, sslFlags); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); // TODO: Do sslFlags match the ones of openssl? STUBBED("Directly pass ssl flags to context"); SSL_CTX_clear_options((SSL_CTX *)emuenv.http.ssl_ctx, sslFlags); return 0; } EXPORT(SceInt, sceHttpsDisableOption2, SceInt tmplId, SceHttpsFlags sslFlags) { TRACY_FUNC(sceHttpsDisableOption2, tmplId, sslFlags); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.templates.contains(tmplId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto tmpl = emuenv.http.templates.find(tmplId); // TODO: Do sslFlags match the ones of openssl? STUBBED("Directly pass ssl flags to context"); SSL_CTX *ctx = SSL_get_SSL_CTX((SSL *)tmpl->second.ssl); SSL_CTX_clear_options(ctx, sslFlags); return 0; } EXPORT(int, sceHttpsDisableOptionPrivate) { TRACY_FUNC(sceHttpsDisableOptionPrivate); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpsEnableOption2, SceInt tmplId, SceHttpsFlags sslFlags) { TRACY_FUNC(sceHttpsEnableOption2, tmplId, sslFlags); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.templates.contains(tmplId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); auto tmpl = emuenv.http.templates.find(tmplId); // TODO: Do sslFlags match the ones of openssl? STUBBED("Directly pass ssl flags to context"); SSL_CTX *ctx = SSL_get_SSL_CTX((SSL *)tmpl->second.ssl); SSL_CTX_set_options(ctx, sslFlags); return 0; } EXPORT(SceInt, sceHttpsEnableOption, SceHttpsFlags sslFlags) { TRACY_FUNC(sceHttpsEnableOption, sslFlags); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); // TODO: Do sslFlags match the ones of openssl? STUBBED("Directly pass ssl flags to context"); SSL_CTX_set_options((SSL_CTX *)emuenv.http.ssl_ctx, sslFlags); return 0; } EXPORT(int, sceHttpsEnableOptionPrivate) { TRACY_FUNC(sceHttpsEnableOptionPrivate); return UNIMPLEMENTED(); } EXPORT(int, sceHttpsFreeCaList) { TRACY_FUNC(sceHttpsFreeCaList); return UNIMPLEMENTED(); } EXPORT(int, sceHttpsGetCaList) { TRACY_FUNC(sceHttpsGetCaList); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpsGetSslError, SceInt tmplId, SceInt *errNum, SceUInt *detail) { TRACY_FUNC(sceHttpsGetSslError, tmplId, errNum, detail); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.templates.contains(tmplId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); return UNIMPLEMENTED(); } EXPORT(int, sceHttpsLoadCert) { TRACY_FUNC(sceHttpsLoadCert); return UNIMPLEMENTED(); } EXPORT(SceInt, sceHttpsSetSslCallback, SceInt tmplId, SceHttpsCallback cbFunction, void *userArg) { TRACY_FUNC(sceHttpsSetSslCallback, tmplId, cbFunction, userArg); if (!emuenv.http.inited) return RET_ERROR(SCE_HTTP_ERROR_BEFORE_INIT); if (!emuenv.http.templates.contains(tmplId)) return RET_ERROR(SCE_HTTP_ERROR_INVALID_ID); if (!cbFunction) return RET_ERROR(SCE_HTTP_ERROR_INVALID_VALUE); return UNIMPLEMENTED(); } EXPORT(int, sceHttpsUnloadCert) { TRACY_FUNC(sceHttpsUnloadCert); return UNIMPLEMENTED(); }