mirror of
https://github.com/Vita3K/Vita3K.git
synced 2025-04-02 11:02:10 -04:00
1657 lines
53 KiB
C++
1657 lines
53 KiB
C++
// 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 <module/module.h>
|
|
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <http/state.h>
|
|
|
|
#ifdef _WIN32 // windows moment
|
|
#include <io.h>
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#define write(x, y, z) _write(x, y, z)
|
|
#define read(x, y, z) _read(x, y, z)
|
|
#else
|
|
#include <netdb.h>
|
|
#include <sys/socket.h>
|
|
#endif
|
|
|
|
#include <kernel/state.h>
|
|
#include <net/state.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/ssl.h>
|
|
#include <util/lock_and_find.h>
|
|
#include <util/log.h>
|
|
#include <util/net_utils.h>
|
|
#include <util/string_utils.h>
|
|
#include <util/tracy.h>
|
|
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
TRACY_MODULE_NAME(SceHttp);
|
|
|
|
template <>
|
|
std::string to_debug_str<SceHttpAddHeaderMode>(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<SceHttpVersion>(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<SceHttpMethods>(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<SceHttpsFlags>(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<char> *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<char>(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<const char> headers, SceSize headersLen, const char *fieldStr, Ptr<char> *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<char>(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<char> *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<char>(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<char> *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<char>(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<void> 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<char> &field, const std::string &value) {
|
|
field = Ptr<char>(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();
|
|
}
|