mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-07-10 02:04:42 -06:00
Implemented MultipartPart content for npwebapi (#4663)
* implemented MultipartPart content * fixed possible duplicate content-type resubmit
This commit is contained in:
parent
5b29e8cd95
commit
d460c94b60
@ -180,15 +180,9 @@ s32 PS4_SYSV_ABI sceNpWebApiAddHttpRequestHeader(s64 requestId, const char* pFie
|
||||
s32 PS4_SYSV_ABI sceNpWebApiAddMultipartPart(s64 requestId,
|
||||
const OrbisNpWebApiMultipartPartParameter* pParam,
|
||||
s32* pIndex) {
|
||||
LOG_INFO(Lib_NpWebApi,
|
||||
"called (STUBBED) : requestId = {:#x}, "
|
||||
"pParam = {}, pIndex = {}",
|
||||
requestId, fmt::ptr(pParam), fmt::ptr(pIndex));
|
||||
if (pParam) {
|
||||
LOG_ERROR(Lib_NpWebApi, " Part params: headerNum = {}, contentLength = {}",
|
||||
pParam->headerNum, pParam->contentLength);
|
||||
}
|
||||
return ORBIS_OK;
|
||||
LOG_INFO(Lib_NpWebApi, "called : requestId = {:#x}, headerNum = {}, contentLength = {}",
|
||||
requestId, (pParam ? pParam->headerNum : 0), (pParam ? pParam->contentLength : 0));
|
||||
return addMultipartPart(requestId, pParam, pIndex);
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceNpWebApiCheckTimeout() {
|
||||
@ -570,11 +564,9 @@ s32 PS4_SYSV_ABI sceNpWebApiSetMaxConnection(s32 libCtxId, s32 maxConnection) {
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpWebApiSetMultipartContentType(s64 requestId, const char* pTypeName,
|
||||
const char* pBoundary) {
|
||||
LOG_ERROR(Lib_NpWebApi,
|
||||
"called (STUBBED) : requestId = {:#x}, "
|
||||
"pTypeName = '{}', pBoundary = '{}'",
|
||||
requestId, (pTypeName ? pTypeName : "null"), (pBoundary ? pBoundary : "null"));
|
||||
return ORBIS_OK;
|
||||
LOG_INFO(Lib_NpWebApi, "called : requestId = {:#x}, pTypeName = '{}', pBoundary = '{}'",
|
||||
requestId, (pTypeName ? pTypeName : "null"), (pBoundary ? pBoundary : "null"));
|
||||
return setMultipartContentType(requestId, pTypeName, pBoundary);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpWebApiSetRequestTimeout(s64 requestId, u32 timeout) {
|
||||
|
||||
@ -579,6 +579,94 @@ void checkRequestTimeout(OrbisNpWebApiRequest* request) {
|
||||
}
|
||||
}
|
||||
|
||||
s32 setMultipartContentType(s64 requestId, const char* pTypeName, const char* pBoundary) {
|
||||
OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
|
||||
if (context == nullptr) {
|
||||
return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
|
||||
}
|
||||
OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20);
|
||||
if (user_context == nullptr) {
|
||||
releaseContext(context);
|
||||
return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
|
||||
}
|
||||
OrbisNpWebApiRequest* request = findRequest(user_context, requestId);
|
||||
if (request == nullptr) {
|
||||
releaseUserContext(user_context);
|
||||
releaseContext(context);
|
||||
return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND;
|
||||
}
|
||||
request->multipartContentType = (pTypeName != nullptr) ? pTypeName : "";
|
||||
request->multipartBoundary = (pBoundary != nullptr) ? pBoundary : "";
|
||||
releaseRequest(request);
|
||||
releaseUserContext(user_context);
|
||||
releaseContext(context);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 addMultipartPart(s64 requestId, const OrbisNpWebApiMultipartPartParameter* pParam,
|
||||
s32* pIndex) {
|
||||
if (pParam == nullptr) {
|
||||
return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
|
||||
if (context == nullptr) {
|
||||
return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
|
||||
}
|
||||
OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20);
|
||||
if (user_context == nullptr) {
|
||||
releaseContext(context);
|
||||
return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
|
||||
}
|
||||
OrbisNpWebApiRequest* request = findRequest(user_context, requestId);
|
||||
if (request == nullptr) {
|
||||
releaseUserContext(user_context);
|
||||
releaseContext(context);
|
||||
return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND;
|
||||
}
|
||||
|
||||
// The library supplies Content-Length itself (from contentLength), so skip an app-provided one.
|
||||
const auto isContentLength = [](const char* name) {
|
||||
const char* t = "content-length";
|
||||
for (; *name != '\0' && *t != '\0'; ++name, ++t) {
|
||||
char c = *name;
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c = static_cast<char>(c - 'A' + 'a');
|
||||
}
|
||||
if (c != *t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return *name == '\0' && *t == '\0';
|
||||
};
|
||||
|
||||
OrbisNpWebApiRequest::MultipartPart part;
|
||||
part.contentLength = pParam->contentLength;
|
||||
std::string headers;
|
||||
for (u64 i = 0; i < pParam->headerNum; ++i) {
|
||||
const OrbisNpWebApiHttpHeader& h = pParam->pHeaders[i];
|
||||
if (h.pName == nullptr || h.pValue == nullptr || isContentLength(h.pName)) {
|
||||
continue;
|
||||
}
|
||||
headers += h.pName;
|
||||
headers += ": ";
|
||||
headers += h.pValue;
|
||||
headers += "\r\n";
|
||||
}
|
||||
headers += "Content-Length: ";
|
||||
headers += std::to_string(pParam->contentLength);
|
||||
headers += "\r\n\r\n";
|
||||
part.rawHeaders = std::move(headers);
|
||||
|
||||
request->multipartParts.push_back(std::move(part));
|
||||
if (pIndex != nullptr) {
|
||||
*pIndex = static_cast<s32>(request->multipartParts.size()); // 1-based part index
|
||||
}
|
||||
releaseRequest(request);
|
||||
releaseUserContext(user_context);
|
||||
releaseContext(context);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s8 flag,
|
||||
OrbisNpWebApiResponseInformationOption* pRespInfoOption) {
|
||||
OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
|
||||
@ -601,7 +689,69 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
|
||||
|
||||
startRequestTimer(request);
|
||||
|
||||
// TODO: multipart logic
|
||||
// Multipart requests accumulate each part payload across sendMultipartRequest() calls,the
|
||||
// full multipart/mixed body is framed and transmitted once every declared part is complete.
|
||||
std::string multipartBody;
|
||||
const void* sendData = pData;
|
||||
u64 sendSize = dataSize;
|
||||
if (request->multipart) {
|
||||
if (partIndex < 1 || partIndex > static_cast<s32>(request->multipartParts.size())) {
|
||||
releaseRequest(request);
|
||||
releaseUserContext(user_context);
|
||||
releaseContext(context);
|
||||
return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
auto& part = request->multipartParts[partIndex - 1];
|
||||
if (pData != nullptr && dataSize > 0) {
|
||||
part.data.append(static_cast<const char*>(pData), dataSize);
|
||||
}
|
||||
part.sentSize += dataSize;
|
||||
|
||||
bool allComplete = true;
|
||||
for (const auto& p : request->multipartParts) {
|
||||
if (p.sentSize < p.contentLength) {
|
||||
allComplete = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allComplete) {
|
||||
// Further part payloads still expected; nothing to transmit yet.
|
||||
releaseRequest(request);
|
||||
releaseUserContext(user_context);
|
||||
releaseContext(context);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
std::string boundary = request->multipartBoundary;
|
||||
if (boundary.empty()) {
|
||||
// Fallback boundary derived from the unique request id.
|
||||
static const char kHex[] = "0123456789abcdef";
|
||||
boundary = "----shadPS4Boundary";
|
||||
const u64 v = static_cast<u64>(requestId);
|
||||
for (int shift = 60; shift >= 0; shift -= 4) {
|
||||
boundary += kHex[(v >> shift) & 0xf];
|
||||
}
|
||||
}
|
||||
const std::string typeName = request->multipartContentType.empty()
|
||||
? std::string("multipart/mixed")
|
||||
: request->multipartContentType;
|
||||
for (const auto& p : request->multipartParts) {
|
||||
multipartBody += "--";
|
||||
multipartBody += boundary;
|
||||
multipartBody += "\r\n";
|
||||
multipartBody += p.rawHeaders; // header lines + blank line separating headers/body
|
||||
multipartBody += p.data;
|
||||
multipartBody += "\r\n";
|
||||
}
|
||||
multipartBody += "--";
|
||||
multipartBody += boundary;
|
||||
multipartBody += "--\r\n";
|
||||
|
||||
request->userContentType = typeName + "; boundary=" + boundary;
|
||||
request->userContentLength = multipartBody.size();
|
||||
sendData = multipartBody.data();
|
||||
sendSize = multipartBody.size();
|
||||
}
|
||||
|
||||
if (g_sdk_ver >= Common::ElfInfo::FW_250 && !request->sent) {
|
||||
request->sent = true;
|
||||
@ -723,8 +873,30 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
|
||||
user_context->userId, request->userPath);
|
||||
}
|
||||
|
||||
// Replay app-supplied headers
|
||||
// Replay app-supplied headers. Content-Type is already emitted above from the
|
||||
// ContentParameter (userContentType),skip a duplicate app-supplied Content-Type so the
|
||||
// request never carries two
|
||||
const auto isContentType = [](const std::string& name) {
|
||||
constexpr std::string_view target = "content-type";
|
||||
if (name.size() != target.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < name.size(); ++i) {
|
||||
char c = name[i];
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c = static_cast<char>(c - 'A' + 'a');
|
||||
}
|
||||
if (c != target[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const bool haveContentType = !request->userContentType.empty();
|
||||
for (const auto& [hname, hvalue] : request->userHeaders) {
|
||||
if (haveContentType && isContentType(hname)) {
|
||||
continue;
|
||||
}
|
||||
Libraries::Http::sceHttpAddRequestHeader(req_id, hname.c_str(), hvalue.c_str(),
|
||||
/*mode=*/0);
|
||||
}
|
||||
@ -733,7 +905,7 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
|
||||
setRequestState(request, 4);
|
||||
|
||||
const s32 send_err =
|
||||
Libraries::Http::sceHttpSendRequest(request->http_request_id, pData, dataSize);
|
||||
Libraries::Http::sceHttpSendRequest(request->http_request_id, sendData, sendSize);
|
||||
if (send_err < 0) {
|
||||
LOG_ERROR(Lib_NpWebApi, "sendRequest: sceHttpSendRequest failed: {:#x}", send_err);
|
||||
releaseRequest(request);
|
||||
|
||||
@ -70,6 +70,17 @@ struct OrbisNpWebApiRequest {
|
||||
// the libSceHttp request in the send path after the lib-managed CT/Authorization.
|
||||
std::vector<std::pair<std::string, std::string>> userHeaders;
|
||||
bool multipart;
|
||||
// Multipart body assembly (sceNpWebApiSetMultipartContentType / AddMultipartPart /
|
||||
// SendMultipartRequest). Parts accumulate across sends,the body is framed once all are in.
|
||||
struct MultipartPart {
|
||||
std::string rawHeaders; // "<name>: <value>\r\n...Content-Length: <n>\r\n\r\n"
|
||||
u64 contentLength = 0;
|
||||
std::string data;
|
||||
u64 sentSize = 0;
|
||||
};
|
||||
std::string multipartContentType; // type name, e.g. "multipart/mixed"
|
||||
std::string multipartBoundary;
|
||||
std::vector<MultipartPart> multipartParts;
|
||||
bool aborted;
|
||||
bool sent;
|
||||
u32 requestTimeout;
|
||||
@ -203,6 +214,8 @@ void startRequestTimer(OrbisNpWebApiRequest* request); // FUN_0100c0d0
|
||||
void checkRequestTimeout(OrbisNpWebApiRequest* request); // FUN_0100c130
|
||||
s32 sendRequest(s64 requestId, s32 partIndex, const void* data, u64 dataSize, s8 flag,
|
||||
OrbisNpWebApiResponseInformationOption* pResponseInformationOption); // FUN_01001c50
|
||||
s32 setMultipartContentType(s64 requestId, const char* pTypeName, const char* pBoundary);
|
||||
s32 addMultipartPart(s64 requestId, const OrbisNpWebApiMultipartPartParameter* pParam, s32* pIndex);
|
||||
s32 abortRequestInternal(OrbisNpWebApiContext* context, OrbisNpWebApiUserContext* userContext,
|
||||
OrbisNpWebApiRequest* request); // FUN_01001b70
|
||||
s32 abortRequest(s64 requestId); // FUN_01002c70
|
||||
|
||||
Loading…
Reference in New Issue
Block a user