Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/ps/ModIo.cpp
Show First 20 Lines • Show All 580 Lines • ▼ Show 20 Lines | |||||
* { "data": [{"id": 42, ...}, ...], ... } | * { "data": [{"id": 42, ...}, ...], ... } | ||||
* where we are only interested in the value of the id property. | * where we are only interested in the value of the id property. | ||||
* | * | ||||
* @returns true iff it successfully parsed the id. | * @returns true iff it successfully parsed the id. | ||||
*/ | */ | ||||
bool ModIo::ParseGameIdResponse(const ScriptInterface& scriptInterface, const std::string& responseData, int& id, std::string& err) | bool ModIo::ParseGameIdResponse(const ScriptInterface& scriptInterface, const std::string& responseData, int& id, std::string& err) | ||||
{ | { | ||||
#define CLEANUP() id = -1; | #define CLEANUP() id = -1; | ||||
JSContext* cx = scriptInterface.GetContext(); | ScriptInterface::Request rq(scriptInterface); | ||||
JSAutoRequest rq(cx); | |||||
JS::RootedValue gameResponse(cx); | JS::RootedValue gameResponse(rq.cx); | ||||
if (!scriptInterface.ParseJSON(responseData, &gameResponse)) | if (!scriptInterface.ParseJSON(responseData, &gameResponse)) | ||||
FAIL("Failed to parse response as JSON."); | FAIL("Failed to parse response as JSON."); | ||||
if (!gameResponse.isObject()) | if (!gameResponse.isObject()) | ||||
FAIL("response not an object."); | FAIL("response not an object."); | ||||
JS::RootedObject gameResponseObj(cx, gameResponse.toObjectOrNull()); | JS::RootedObject gameResponseObj(rq.cx, gameResponse.toObjectOrNull()); | ||||
JS::RootedValue dataVal(cx); | JS::RootedValue dataVal(rq.cx); | ||||
if (!JS_GetProperty(cx, gameResponseObj, "data", &dataVal)) | if (!JS_GetProperty(rq.cx, gameResponseObj, "data", &dataVal)) | ||||
FAIL("data property not in response."); | FAIL("data property not in response."); | ||||
// [{"id": 42, ...}, ...] | // [{"id": 42, ...}, ...] | ||||
if (!dataVal.isObject()) | if (!dataVal.isObject()) | ||||
FAIL("data property not an object."); | FAIL("data property not an object."); | ||||
JS::RootedObject data(cx, dataVal.toObjectOrNull()); | JS::RootedObject data(rq.cx, dataVal.toObjectOrNull()); | ||||
u32 length; | u32 length; | ||||
bool isArray; | bool isArray; | ||||
if (!JS_IsArrayObject(cx, data, &isArray) || !isArray || !JS_GetArrayLength(cx, data, &length) || !length) | if (!JS_IsArrayObject(rq.cx, data, &isArray) || !isArray || !JS_GetArrayLength(rq.cx, data, &length) || !length) | ||||
FAIL("data property not an array with at least one element."); | FAIL("data property not an array with at least one element."); | ||||
// {"id": 42, ...} | // {"id": 42, ...} | ||||
JS::RootedValue first(cx); | JS::RootedValue first(rq.cx); | ||||
if (!JS_GetElement(cx, data, 0, &first)) | if (!JS_GetElement(rq.cx, data, 0, &first)) | ||||
FAIL("Couldn't get first element."); | FAIL("Couldn't get first element."); | ||||
if (!first.isObject()) | if (!first.isObject()) | ||||
FAIL("First element not an object."); | FAIL("First element not an object."); | ||||
JS::RootedObject firstObj(cx, &first.toObject()); | JS::RootedObject firstObj(rq.cx, &first.toObject()); | ||||
bool hasIdProperty; | bool hasIdProperty; | ||||
if (!JS_HasProperty(cx, firstObj, "id", &hasIdProperty) || !hasIdProperty) | if (!JS_HasProperty(rq.cx, firstObj, "id", &hasIdProperty) || !hasIdProperty) | ||||
FAIL("No id property in first element."); | FAIL("No id property in first element."); | ||||
JS::RootedValue idProperty(cx); | JS::RootedValue idProperty(rq.cx); | ||||
ENSURE(JS_GetProperty(cx, firstObj, "id", &idProperty)); | ENSURE(JS_GetProperty(rq.cx, firstObj, "id", &idProperty)); | ||||
// Make sure the property is not set to something that could be converted to a bogus value | // Make sure the property is not set to something that could be converted to a bogus value | ||||
// TODO: We should be able to convert JS::Values to C++ variables in a way that actually | // TODO: We should be able to convert JS::Values to C++ variables in a way that actually | ||||
// fails when types do not match (see https://trac.wildfiregames.com/ticket/5128). | // fails when types do not match (see https://trac.wildfiregames.com/ticket/5128). | ||||
if (!idProperty.isNumber()) | if (!idProperty.isNumber()) | ||||
FAIL("id property not a number."); | FAIL("id property not a number."); | ||||
id = -1; | id = -1; | ||||
if (!ScriptInterface::FromJSVal(cx, idProperty, id) || id <= 0) | if (!ScriptInterface::FromJSVal(rq, idProperty, id) || id <= 0) | ||||
FAIL("Invalid id."); | FAIL("Invalid id."); | ||||
return true; | return true; | ||||
#undef CLEANUP | #undef CLEANUP | ||||
} | } | ||||
/** | /** | ||||
* Parses the current content of m_ResponseData into m_ModData. | * Parses the current content of m_ResponseData into m_ModData. | ||||
* | * | ||||
* The JSON data is expected to look like | * The JSON data is expected to look like | ||||
* { data: [modobj1, modobj2, ...], ... (including result_count) } | * { data: [modobj1, modobj2, ...], ... (including result_count) } | ||||
* where modobjN has the following structure | * where modobjN has the following structure | ||||
* { homepage_url: "url", name: "displayname", nameid: "short-non-whitespace-name", | * { homepage_url: "url", name: "displayname", nameid: "short-non-whitespace-name", | ||||
* summary: "short desc.", modfile: { version: "1.2.4", filename: "asdf.zip", | * summary: "short desc.", modfile: { version: "1.2.4", filename: "asdf.zip", | ||||
* filehash: { md5: "deadbeef" }, filesize: 1234, download: { binary_url: "someurl", ... } }, ... }. | * filehash: { md5: "deadbeef" }, filesize: 1234, download: { binary_url: "someurl", ... } }, ... }. | ||||
* Only the listed properties are of interest to consumers, and we flatten | * Only the listed properties are of interest to consumers, and we flatten | ||||
* the modfile structure as that simplifies handling and there are no conflicts. | * the modfile structure as that simplifies handling and there are no conflicts. | ||||
*/ | */ | ||||
bool ModIo::ParseModsResponse(const ScriptInterface& scriptInterface, const std::string& responseData, std::vector<ModIoModData>& modData, const PKStruct& pk, std::string& err) | bool ModIo::ParseModsResponse(const ScriptInterface& scriptInterface, const std::string& responseData, std::vector<ModIoModData>& modData, const PKStruct& pk, std::string& err) | ||||
{ | { | ||||
// Make sure we don't end up passing partial results back | // Make sure we don't end up passing partial results back | ||||
#define CLEANUP() modData.clear(); | #define CLEANUP() modData.clear(); | ||||
JSContext* cx = scriptInterface.GetContext(); | ScriptInterface::Request rq(scriptInterface); | ||||
JSAutoRequest rq(cx); | |||||
JS::RootedValue modResponse(cx); | JS::RootedValue modResponse(rq.cx); | ||||
if (!scriptInterface.ParseJSON(responseData, &modResponse)) | if (!scriptInterface.ParseJSON(responseData, &modResponse)) | ||||
FAIL("Failed to parse response as JSON."); | FAIL("Failed to parse response as JSON."); | ||||
if (!modResponse.isObject()) | if (!modResponse.isObject()) | ||||
FAIL("response not an object."); | FAIL("response not an object."); | ||||
JS::RootedObject modResponseObj(cx, modResponse.toObjectOrNull()); | JS::RootedObject modResponseObj(rq.cx, modResponse.toObjectOrNull()); | ||||
JS::RootedValue dataVal(cx); | JS::RootedValue dataVal(rq.cx); | ||||
if (!JS_GetProperty(cx, modResponseObj, "data", &dataVal)) | if (!JS_GetProperty(rq.cx, modResponseObj, "data", &dataVal)) | ||||
FAIL("data property not in response."); | FAIL("data property not in response."); | ||||
// [modobj1, modobj2, ... ] | // [modobj1, modobj2, ... ] | ||||
if (!dataVal.isObject()) | if (!dataVal.isObject()) | ||||
FAIL("data property not an object."); | FAIL("data property not an object."); | ||||
JS::RootedObject rData(cx, dataVal.toObjectOrNull()); | JS::RootedObject rData(rq.cx, dataVal.toObjectOrNull()); | ||||
u32 length; | u32 length; | ||||
bool isArray; | bool isArray; | ||||
if (!JS_IsArrayObject(cx, rData, &isArray) || !isArray || !JS_GetArrayLength(cx, rData, &length) || !length) | if (!JS_IsArrayObject(rq.cx, rData, &isArray) || !isArray || !JS_GetArrayLength(rq.cx, rData, &length) || !length) | ||||
FAIL("data property not an array with at least one element."); | FAIL("data property not an array with at least one element."); | ||||
modData.clear(); | modData.clear(); | ||||
modData.reserve(length); | modData.reserve(length); | ||||
#define INVALIDATE_DATA_AND_CONTINUE(...) \ | #define INVALIDATE_DATA_AND_CONTINUE(...) \ | ||||
{\ | {\ | ||||
data.properties.emplace("invalid", "true");\ | data.properties.emplace("invalid", "true");\ | ||||
data.properties.emplace("error", __VA_ARGS__);\ | data.properties.emplace("error", __VA_ARGS__);\ | ||||
continue;\ | continue;\ | ||||
} | } | ||||
for (u32 i = 0; i < length; ++i) | for (u32 i = 0; i < length; ++i) | ||||
{ | { | ||||
modData.emplace_back(); | modData.emplace_back(); | ||||
ModIoModData& data = modData.back(); | ModIoModData& data = modData.back(); | ||||
JS::RootedValue el(cx); | JS::RootedValue el(rq.cx); | ||||
if (!JS_GetElement(cx, rData, i, &el) || !el.isObject()) | if (!JS_GetElement(rq.cx, rData, i, &el) || !el.isObject()) | ||||
INVALIDATE_DATA_AND_CONTINUE("Failed to get array element object.") | INVALIDATE_DATA_AND_CONTINUE("Failed to get array element object.") | ||||
bool ok = true; | bool ok = true; | ||||
std::string copyStringError; | std::string copyStringError; | ||||
#define COPY_STRINGS_ELSE_CONTINUE(prefix, obj, ...) \ | #define COPY_STRINGS_ELSE_CONTINUE(prefix, obj, ...) \ | ||||
for (const std::string& prop : { __VA_ARGS__ }) \ | for (const std::string& prop : { __VA_ARGS__ }) \ | ||||
{ \ | { \ | ||||
std::string val; \ | std::string val; \ | ||||
if (!ScriptInterface::FromJSProperty(cx, obj, prop.c_str(), val, true)) \ | if (!ScriptInterface::FromJSProperty(rq, obj, prop.c_str(), val, true)) \ | ||||
{ \ | { \ | ||||
ok = false; \ | ok = false; \ | ||||
copyStringError = "Failed to get " + prop + " from " + #obj + "."; \ | copyStringError = "Failed to get " + prop + " from " + #obj + "."; \ | ||||
break; \ | break; \ | ||||
}\ | }\ | ||||
data.properties.emplace(prefix+prop, val); \ | data.properties.emplace(prefix+prop, val); \ | ||||
} \ | } \ | ||||
if (!ok) \ | if (!ok) \ | ||||
INVALIDATE_DATA_AND_CONTINUE(copyStringError); | INVALIDATE_DATA_AND_CONTINUE(copyStringError); | ||||
// TODO: Currently the homepage_url field does not contain a non-null value for any entry. | // TODO: Currently the homepage_url field does not contain a non-null value for any entry. | ||||
COPY_STRINGS_ELSE_CONTINUE("", el, "name", "name_id", "summary") | COPY_STRINGS_ELSE_CONTINUE("", el, "name", "name_id", "summary") | ||||
// Now copy over the modfile part, but without the pointless substructure | // Now copy over the modfile part, but without the pointless substructure | ||||
JS::RootedObject elObj(cx, el.toObjectOrNull()); | JS::RootedObject elObj(rq.cx, el.toObjectOrNull()); | ||||
JS::RootedValue modFile(cx); | JS::RootedValue modFile(rq.cx); | ||||
if (!JS_GetProperty(cx, elObj, "modfile", &modFile)) | if (!JS_GetProperty(rq.cx, elObj, "modfile", &modFile)) | ||||
INVALIDATE_DATA_AND_CONTINUE("Failed to get modfile data."); | INVALIDATE_DATA_AND_CONTINUE("Failed to get modfile data."); | ||||
if (!modFile.isObject()) | if (!modFile.isObject()) | ||||
INVALIDATE_DATA_AND_CONTINUE("modfile not an object."); | INVALIDATE_DATA_AND_CONTINUE("modfile not an object."); | ||||
COPY_STRINGS_ELSE_CONTINUE("", modFile, "version", "filesize"); | COPY_STRINGS_ELSE_CONTINUE("", modFile, "version", "filesize"); | ||||
JS::RootedObject modFileObj(cx, modFile.toObjectOrNull()); | JS::RootedObject modFileObj(rq.cx, modFile.toObjectOrNull()); | ||||
JS::RootedValue filehash(cx); | JS::RootedValue filehash(rq.cx); | ||||
if (!JS_GetProperty(cx, modFileObj, "filehash", &filehash)) | if (!JS_GetProperty(rq.cx, modFileObj, "filehash", &filehash)) | ||||
INVALIDATE_DATA_AND_CONTINUE("Failed to get filehash data."); | INVALIDATE_DATA_AND_CONTINUE("Failed to get filehash data."); | ||||
COPY_STRINGS_ELSE_CONTINUE("filehash_", filehash, "md5"); | COPY_STRINGS_ELSE_CONTINUE("filehash_", filehash, "md5"); | ||||
JS::RootedValue download(cx); | JS::RootedValue download(rq.cx); | ||||
if (!JS_GetProperty(cx, modFileObj, "download", &download)) | if (!JS_GetProperty(rq.cx, modFileObj, "download", &download)) | ||||
INVALIDATE_DATA_AND_CONTINUE("Failed to get download data."); | INVALIDATE_DATA_AND_CONTINUE("Failed to get download data."); | ||||
COPY_STRINGS_ELSE_CONTINUE("", download, "binary_url"); | COPY_STRINGS_ELSE_CONTINUE("", download, "binary_url"); | ||||
// Parse metadata_blob (sig+deps) | // Parse metadata_blob (sig+deps) | ||||
std::string metadata_blob; | std::string metadata_blob; | ||||
if (!ScriptInterface::FromJSProperty(cx, modFile, "metadata_blob", metadata_blob, true)) | if (!ScriptInterface::FromJSProperty(rq, modFile, "metadata_blob", metadata_blob, true)) | ||||
INVALIDATE_DATA_AND_CONTINUE("Failed to get metadata_blob from modFile."); | INVALIDATE_DATA_AND_CONTINUE("Failed to get metadata_blob from modFile."); | ||||
JS::RootedValue metadata(cx); | JS::RootedValue metadata(rq.cx); | ||||
if (!scriptInterface.ParseJSON(metadata_blob, &metadata)) | if (!scriptInterface.ParseJSON(metadata_blob, &metadata)) | ||||
INVALIDATE_DATA_AND_CONTINUE("Failed to parse metadata_blob as JSON."); | INVALIDATE_DATA_AND_CONTINUE("Failed to parse metadata_blob as JSON."); | ||||
if (!metadata.isObject()) | if (!metadata.isObject()) | ||||
INVALIDATE_DATA_AND_CONTINUE("metadata_blob is not decoded as an object."); | INVALIDATE_DATA_AND_CONTINUE("metadata_blob is not decoded as an object."); | ||||
if (!ScriptInterface::FromJSProperty(cx, metadata, "dependencies", data.dependencies, true)) | if (!ScriptInterface::FromJSProperty(rq, metadata, "dependencies", data.dependencies, true)) | ||||
INVALIDATE_DATA_AND_CONTINUE("Failed to get dependencies from metadata_blob."); | INVALIDATE_DATA_AND_CONTINUE("Failed to get dependencies from metadata_blob."); | ||||
std::vector<std::string> minisigs; | std::vector<std::string> minisigs; | ||||
if (!ScriptInterface::FromJSProperty(cx, metadata, "minisigs", minisigs, true)) | if (!ScriptInterface::FromJSProperty(rq, metadata, "minisigs", minisigs, true)) | ||||
INVALIDATE_DATA_AND_CONTINUE("Failed to get minisigs from metadata_blob."); | INVALIDATE_DATA_AND_CONTINUE("Failed to get minisigs from metadata_blob."); | ||||
// Check we did find a valid matching signature. | // Check we did find a valid matching signature. | ||||
std::string signatureParsingErr; | std::string signatureParsingErr; | ||||
if (!ParseSignature(minisigs, data.sig, pk, signatureParsingErr)) | if (!ParseSignature(minisigs, data.sig, pk, signatureParsingErr)) | ||||
INVALIDATE_DATA_AND_CONTINUE(signatureParsingErr); | INVALIDATE_DATA_AND_CONTINUE(signatureParsingErr); | ||||
#undef COPY_STRINGS_ELSE_CONTINUE | #undef COPY_STRINGS_ELSE_CONTINUE | ||||
▲ Show 20 Lines • Show All 84 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator