diff --git a/src/networksystem/bansystem.cpp b/src/networksystem/bansystem.cpp index 2714dd77..6311760c 100644 --- a/src/networksystem/bansystem.cpp +++ b/src/networksystem/bansystem.cpp @@ -7,6 +7,7 @@ #include "core/stdafx.h" #include "tier1/strtools.h" +#include "tier2/jsonutils.h" #include "engine/net.h" #include "engine/server/server.h" #include "engine/client/client.h" @@ -47,25 +48,33 @@ void CBanSystem::LoadList(void) return; } - uint64_t nTotalBans = 0; - if (document.HasMember("totalBans") && document["totalBans"].IsUint64()) + int nTotalBans = 0; + + if (!JSON_GetValue(document, "totalBans", JSONFieldType_e::kSint32, nTotalBans)) { - nTotalBans = document["totalBans"].GetUint64(); + return; } - for (uint64_t i = 0; i < nTotalBans; i++) + for (int i = 0; i < nTotalBans; i++) { - char idx[64]; _ui64toa(i, idx, 10); + char idx[12]; itoa(i, idx, 10); - if (document.HasMember(idx) && document[idx].IsObject()) + rapidjson::Value::ConstMemberIterator entryIt; + + if (JSON_GetIterator(document, idx, JSONFieldType_e::kObject, entryIt)) { - const rapidjson::Value& entry = document[idx]; - if (entry.HasMember("ipAddress") && entry["ipAddress"].IsString() && - entry.HasMember("nucleusId") && entry["nucleusId"].IsUint64()) + const rapidjson::Value& entry = entryIt->value; + + const char* ipAddress = nullptr; + NucleusID_t nucleusId = NULL; + + if (JSON_GetValue(entry, "ipAddress", JSONFieldType_e::kString, ipAddress) && + JSON_GetValue(entry, "nucleusId", JSONFieldType_e::kUint64, nucleusId)) { Banned_t banned; - banned.m_Address = entry["ipAddress"].GetString(); - banned.m_NucleusID = entry["nucleusId"].GetUint64(); + + banned.m_Address = ipAddress; + banned.m_NucleusID = nucleusId; m_BannedList.AddToTail(banned); } @@ -93,9 +102,10 @@ void CBanSystem::SaveList(void) const FOR_EACH_VEC(m_BannedList, i) { const Banned_t& banned = m_BannedList[i]; - char idx[64]; _ui64toa(i, idx, 10); + char idx[12]; itoa(i, idx, 10); rapidjson::Value obj(rapidjson::kObjectType); + obj.AddMember("ipAddress", rapidjson::Value(banned.m_Address.String(), allocator), allocator); obj.AddMember("nucleusId", banned.m_NucleusID, allocator); diff --git a/src/networksystem/pylon.cpp b/src/networksystem/pylon.cpp index 57a53940..9c669f09 100644 --- a/src/networksystem/pylon.cpp +++ b/src/networksystem/pylon.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -19,23 +20,24 @@ ConVar pylon_host_update_interval("pylon_host_update_interval", "5", FCVAR_RELEA ConVar pylon_showdebuginfo("pylon_showdebuginfo", "0", FCVAR_RELEASE | FCVAR_ACCESSIBLE_FROM_THREADS, "Shows debug output for pylon"); //----------------------------------------------------------------------------- -// Purpose: checks if the server listing fields are valid. +// Purpose: checks if server listing fields are valid, and sets outGameServer // Input : &value - +// &outGameServer - // Output : true on success, false on failure. //----------------------------------------------------------------------------- -static bool IsServerListingValid(const rapidjson::Value& value) +static bool GetServerListingFromJSON(const rapidjson::Value& value, NetGameServer_t& outGameServer) { - if (value.HasMember("name") && value["name"].IsString() && - value.HasMember("description") && value["description"].IsString() && - value.HasMember("hidden") && value["hidden"].IsBool() && - value.HasMember("map") && value["map"].IsString() && - value.HasMember("playlist") && value["playlist"].IsString() && - value.HasMember("ip") && value["ip"].IsString() && - value.HasMember("port") && value["port"].IsInt() && - value.HasMember("key") && value["key"].IsString() && - value.HasMember("checksum") && value["checksum"].IsUint() && - value.HasMember("numPlayers") && value["numPlayers"].IsInt() && - value.HasMember("maxPlayers") && value["maxPlayers"].IsInt()) + if (JSON_GetValue(value, "name", JSONFieldType_e::kString, outGameServer.name) && + JSON_GetValue(value, "description", JSONFieldType_e::kString, outGameServer.description) && + JSON_GetValue(value, "hidden", JSONFieldType_e::kBool, outGameServer.hidden) && + JSON_GetValue(value, "map", JSONFieldType_e::kString, outGameServer.map) && + JSON_GetValue(value, "playlist", JSONFieldType_e::kString, outGameServer.playlist) && + JSON_GetValue(value, "ip", JSONFieldType_e::kString, outGameServer.address) && + JSON_GetValue(value, "port", JSONFieldType_e::kSint32, outGameServer.port) && + JSON_GetValue(value, "key", JSONFieldType_e::kString, outGameServer.netKey) && + JSON_GetValue(value, "checksum", JSONFieldType_e::kUint32, outGameServer.checksum) && + JSON_GetValue(value, "numPlayers", JSONFieldType_e::kSint32, outGameServer.numPlayers) && + JSON_GetValue(value, "maxPlayers", JSONFieldType_e::kSint32, outGameServer.maxPlayers)) { return true; } @@ -66,43 +68,27 @@ bool CPylon::GetServerList(vector& outServerList, string& outMe return false; } - if (!responseJson.HasMember("servers")) + rapidjson::Document::ConstMemberIterator serversIt; + + if (!JSON_GetIterator(responseJson, "servers", JSONFieldType_e::kArray, serversIt)) { outMessage = Format("Invalid response with status: %d", int(status)); return false; } - const rapidjson::Value& servers = responseJson["servers"]; + const rapidjson::Value::ConstArray serverArray = serversIt->value.GetArray(); - for (rapidjson::Value::ConstValueIterator itr = servers.Begin(); - itr != servers.End(); ++itr) + for (const rapidjson::Value& obj : serverArray) { - const rapidjson::Value& obj = *itr; + NetGameServer_t gameServer; - if (!IsServerListingValid(obj)) + if (!GetServerListingFromJSON(obj, gameServer)) { // Missing details; skip this server listing. continue; } - outServerList.push_back( - NetGameServer_t - { - obj["name"].GetString(), - obj["description"].GetString(), - obj["hidden"].GetBool(), - obj["map"].GetString(), - obj["playlist"].GetString(), - obj["ip"].GetString(), - obj["port"].GetInt(), - obj["key"].GetString(), - obj["checksum"].GetUint(), - SDK_VERSION, - obj["numPlayers"].GetInt(), - obj["maxPlayers"].GetInt(), - -1, - } - ); + outServerList.push_back(gameServer); } return true; @@ -134,37 +120,22 @@ bool CPylon::GetServerByToken(NetGameServer_t& outGameServer, return false; } - if (!responseJson.HasMember("server")) + rapidjson::Document::ConstMemberIterator serversIt; + + if (!JSON_GetIterator(responseJson, "servers", JSONFieldType_e::kArray, serversIt)) { outMessage = Format("Invalid response with status: %d", int(status)); return false; } - const rapidjson::Value& serverJson = responseJson["server"]; + const rapidjson::Value& serverJson = serversIt->value; - if (!IsServerListingValid(serverJson)) + if (!GetServerListingFromJSON(serverJson, outGameServer)) { outMessage = Format("Invalid server listing data!"); return false; } - outGameServer = NetGameServer_t - { - serverJson["name"].GetString(), - serverJson["description"].GetString(), - serverJson["hidden"].GetBool(), - serverJson["map"].GetString(), - serverJson["playlist"].GetString(), - serverJson["ip"].GetString(), - serverJson["port"].GetInt(), - serverJson["key"].GetString(), - serverJson["checksum"].GetUint(), - SDK_VERSION, - serverJson["numPlayers"].GetInt(), - serverJson["maxPlayers"].GetInt(), - -1, - }; - return true; } @@ -192,7 +163,7 @@ bool CPylon::PostServerHost(string& outMessage, string& outToken, string& outHos requestJson.AddMember("key", rapidjson::Value(netGameServer.netKey.c_str(), allocator), allocator); requestJson.AddMember("checksum", netGameServer.checksum, allocator); requestJson.AddMember("version", rapidjson::Value(netGameServer.versionId.c_str(), allocator), allocator); - requestJson.AddMember("numPlayers", netGameServer.numPlayers, allocator); + requestJson.AddMember("numPlayers", netGameServer.numPlayers, allocator); requestJson.AddMember("maxPlayers", netGameServer.maxPlayers, allocator); requestJson.AddMember("timeStamp", netGameServer.timeStamp, allocator); @@ -206,21 +177,25 @@ bool CPylon::PostServerHost(string& outMessage, string& outToken, string& outHos if (netGameServer.hidden) { - if (!responseJson.HasMember("token") || !responseJson["token"].IsString()) + const char* token = nullptr; + + if (!JSON_GetValue(responseJson, "token", JSONFieldType_e::kString, token)) { outMessage = Format("Invalid response with status: %d", int(status)); outToken.clear(); return false; } - outToken = responseJson["token"].GetString(); + outToken = token; } - if (responseJson.HasMember("ip") && responseJson["ip"].IsString() && - responseJson.HasMember("port") && responseJson["port"].IsInt()) + const char* ip = nullptr; + int port = 0; + + if (JSON_GetValue(responseJson, "ip", JSONFieldType_e::kString, ip) && + JSON_GetValue(responseJson, "port", JSONFieldType_e::kSint32, port)) { - outHostIp = Format("[%s]:%i", - responseJson["ip"].GetString(), responseJson["port"].GetInt()); + outHostIp = Format("[%s]:%i", ip, port); } return true; @@ -244,10 +219,11 @@ bool CPylon::GetBannedList(const CBanSystem::BannedList_t& inBannedVec, CBanSyst FOR_EACH_VEC(inBannedVec, i) { const CBanSystem::Banned_t& banned = inBannedVec[i]; - rapidjson::Value player(rapidjson::kObjectType); + player.AddMember("id", banned.m_NucleusID, allocator); player.AddMember("ip", rapidjson::Value(banned.m_Address.String(), allocator), allocator); + playersArray.PushBack(player, allocator); } @@ -263,19 +239,25 @@ bool CPylon::GetBannedList(const CBanSystem::BannedList_t& inBannedVec, CBanSyst return false; } - if (!responseJson.HasMember("bannedPlayers") || !responseJson["bannedPlayers"].IsArray()) + rapidjson::Value::ConstMemberIterator bannedPlayersIt; + + if (!JSON_GetIterator(responseJson, "bannedPlayers", JSONFieldType_e::kArray, bannedPlayersIt)) { outMessage = Format("Invalid response with status: %d", int(status)); return false; } - const rapidjson::Value& bannedPlayers = responseJson["bannedPlayers"]; - for (const rapidjson::Value& obj : bannedPlayers.GetArray()) + const rapidjson::Value::ConstArray bannedPlayers = bannedPlayersIt->value.GetArray(); + + for (const rapidjson::Value& obj : bannedPlayers) { - CBanSystem::Banned_t banned( - obj.HasMember("reason") ? obj["reason"].GetString() : "#DISCONNECT_BANNED", - obj.HasMember("id") && obj["id"].IsUint64() ? obj["id"].GetUint64() : NucleusID_t(NULL) - ); + const char* reason = nullptr; + JSON_GetValue(obj, "reason", JSONFieldType_e::kString, reason); + + NucleusID_t nuc = NULL; + JSON_GetValue(obj, "id", JSONFieldType_e::kUint64, nuc); + + CBanSystem::Banned_t banned(reason ? reason : "#DISCONNECT_BANNED", nuc); outBannedVec.AddToTail(banned); } @@ -309,11 +291,18 @@ bool CPylon::CheckForBan(const string& ipAddress, const uint64_t nucleusId, cons return false; } - if (responseJson.HasMember("banned") && responseJson["banned"].IsBool()) + bool isBanned = false; + + if (JSON_GetValue(responseJson, "banned", JSONFieldType_e::kBool, isBanned)) { - if (responseJson["banned"].GetBool()) + if (isBanned) { - outReason = responseJson.HasMember("reason") ? responseJson["reason"].GetString() : "#DISCONNECT_BANNED"; + const char* reason = nullptr; + + outReason = JSON_GetValue(responseJson, "reason", JSONFieldType_e::kString, reason) + ? reason + : "#DISCONNECT_BANNED"; + return true; } } @@ -351,39 +340,17 @@ bool CPylon::AuthForConnection(const uint64_t nucleusId, const char* ipAddress, return false; } - if (responseJson.HasMember("token") && responseJson["token"].IsString()) + const char* token = nullptr; + + if (JSON_GetValue(responseJson, "token", JSONFieldType_e::kString, token)) { - outToken = responseJson["token"].GetString(); + outToken = token; return true; } return false; } -//----------------------------------------------------------------------------- -// Purpose: checks if the EULA response fields are valid. -// Input : &doc - -// Output : true on success, false on failure. -//----------------------------------------------------------------------------- -static bool ValidateEULAData(const rapidjson::Document& doc) -{ - if (!doc.HasMember("data") || !doc["data"].IsObject()) - return false; - - const rapidjson::Value& data = doc["data"]; - - if (!data.HasMember("version") || !data["version"].IsInt()) - return false; - - if (!data.HasMember("lang") || !data["lang"].IsString()) - return false; - - if (!data.HasMember("contents") || !data["contents"].IsString()) - return false; - - return true; -} - //----------------------------------------------------------------------------- // Purpose: checks if the accepted EULA is up to date. // Output : true on success, false on failure. @@ -412,16 +379,32 @@ bool CPylon::GetEULA(MSEulaData_t& outData, string& outMessage) const return false; } - if (!ValidateEULAData(responseJson)) + rapidjson::Document::ConstMemberIterator serversIt; + + if (!JSON_GetIterator(responseJson, "data", JSONFieldType_e::kObject, serversIt)) { + outMessage = "missing or invalid data"; return false; } - const rapidjson::Value& data = responseJson["data"]; + const rapidjson::Value& data = serversIt->value; - outData.version = data["version"].GetInt(); - outData.language = data["lang"].GetString(); - outData.contents = data["contents"].GetString(); + int version = 0; + const char* language = nullptr; + const char* contents = nullptr; + + // check if the EULA response fields are valid. + if (!JSON_GetValue(data, "version", JSONFieldType_e::kSint32, version) || + !JSON_GetValue(data, "language", JSONFieldType_e::kString, language) || + !JSON_GetValue(data, "contents", JSONFieldType_e::kString, contents)) + { + outMessage = "schema is invalid"; + return false; + } + + outData.version = version; + outData.language = language; + outData.contents = contents; return true; } @@ -478,9 +461,10 @@ bool CPylon::SendRequest(const char* endpoint, const rapidjson::Document& reques LogBody(responseJson); } - if (responseJson.HasMember("success") && - responseJson["success"].IsBool() && - responseJson["success"].GetBool()) + bool success = false; + + if (JSON_GetValue(responseJson, "success", JSONFieldType_e::kBool, success) + && success) { return true; } @@ -564,10 +548,12 @@ bool CPylon::QueryServer(const char* endpoint, const char* request, void CPylon::ExtractError(const rapidjson::Document& resultJson, string& outMessage, CURLINFO status, const char* errorText) const { - if (resultJson.IsObject() && resultJson.HasMember("error") && - resultJson["error"].IsString()) + const char* error = nullptr; + + if (resultJson.IsObject() && + JSON_GetValue(resultJson, "error", JSONFieldType_e::kString, error)) { - outMessage = resultJson["error"].GetString(); + outMessage = error; } else { diff --git a/src/public/tier0/utility.h b/src/public/tier0/utility.h index f245e34e..3e4c47fa 100644 --- a/src/public/tier0/utility.h +++ b/src/public/tier0/utility.h @@ -89,8 +89,6 @@ string FormatBytes(size_t nBytes); string FormatV(const char* szFormat, va_list args); string Format(const char* szFormat, ...); -void JSON_DocumentToBufferDeserialize(const rapidjson::Document& document, rapidjson::StringBuffer& buffer, unsigned int indent = 4); - ///////////////////////////////////////////////////////////////////////////// // Array template diff --git a/src/tier0/utility.cpp b/src/tier0/utility.cpp index 0c55dfd1..d619b0b0 100644 --- a/src/tier0/utility.cpp +++ b/src/tier0/utility.cpp @@ -1119,16 +1119,6 @@ string Format(const char* szFormat, ...) return result; } -/////////////////////////////////////////////////////////////////////////////// -// For dumping a json document to a string buffer. -void JSON_DocumentToBufferDeserialize(const rapidjson::Document& document, rapidjson::StringBuffer& buffer, unsigned int indent) -{ - rapidjson::PrettyWriter writer(buffer); - - writer.SetIndent(' ', indent); - document.Accept(writer); -} - /////////////////////////////////////////////////////////////////////////////// // For comparing two IPv6 addresses. int CompareIPv6(const IN6_ADDR& ipA, const IN6_ADDR& ipB) diff --git a/src/tier2/CMakeLists.txt b/src/tier2/CMakeLists.txt index 8d3b33ed..2cca1d97 100644 --- a/src/tier2/CMakeLists.txt +++ b/src/tier2/CMakeLists.txt @@ -7,6 +7,7 @@ add_sources( SOURCE_GROUP "Utility" "cryptutils.cpp" "curlutils.cpp" "fileutils.cpp" + "jsonutils.cpp" "meshutils.cpp" "renderutils.cpp" "socketcreator.cpp"