Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/server/g_local.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,75 @@ struct local_game_import_t : game_import_t {

extern local_game_import_t gi;

/*
=============
VerifyEntityString

Validates that an entity string's braces are balanced and that parsing does
not encounter unexpected end-of-file conditions.
=============
*/
inline bool VerifyEntityString(const char* entities) {
const char* or_token;
const char* or_buf = entities;
int braceDepth = 0;
bool parsedEntity = false;

while (1) {
or_token = COM_Parse(&or_buf);
if (!or_buf) {
if (braceDepth > 0) {
gi.Com_ErrorFmt("{}: EOF without closing brace.\n", __FUNCTION__);
return false;
}
if (!parsedEntity) {
gi.Com_ErrorFmt("{}: EOF while expecting opening brace.\n", __FUNCTION__);
return false;
}
break;
}

if (braceDepth == 0) {
if (or_token[0] == '\0')
break;
if (or_token[0] != '{') {
gi.Com_PrintFmt("{}: Found \"{}\" when expecting {{ in override.\n", __FUNCTION__, or_token);
return false;
}

braceDepth = 1;
parsedEntity = true;
continue;
}

if (or_token[0] == '}') {
if (--braceDepth < 0) {
gi.Com_ErrorFmt("{}: Closing brace without opening brace.\n", __FUNCTION__);
return false;
}
continue;
}

or_token = COM_Parse(&or_buf);
if (!or_buf) {
gi.Com_ErrorFmt("{}: EOF without closing brace.\n", __FUNCTION__);
return false;
}
if (or_token[0] == '}') {
gi.Com_ErrorFmt("{}: Closing brace without data.\n", __FUNCTION__);
return false;
}
}

if (braceDepth != 0) {
gi.Com_ErrorFmt("{}: EOF without closing brace.\n", __FUNCTION__);
return false;
}

return true;
}


// =================================================================

// A template class to manage bitflags for any enum type.
Expand Down
46 changes: 0 additions & 46 deletions src/server/gameplay/g_spawn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1770,52 +1770,6 @@ static void FS_Read(void* buffer, int len, FILE* f) {
}


/*
==============
VerifyEntityString
==============
*/
static bool VerifyEntityString(const char* entities) {
const char* or_token;
gentity_t* or_ent = nullptr;
const char* or_buf = entities;
bool or_error = false;

while (1) {
// parse the opening brace
or_token = COM_Parse(&or_buf);
if (!or_buf)
break;
if (or_token[0] != '{') {
gi.Com_PrintFmt("{}: Found \"{}\" when expecting {{ in override.\n", __FUNCTION__, or_token);
return false;
}

while (1) {
// parse key
or_token = COM_Parse(&or_buf);
if (or_token[0] == '}')
break;
if (!or_buf) {
gi.Com_ErrorFmt("{}: EOF without closing brace.\n", __FUNCTION__);
return false;
}
// parse value
or_token = COM_Parse(&or_buf);
if (!or_buf) {
gi.Com_ErrorFmt("{}: EOF without closing brace.\n", __FUNCTION__);
return false;
}
if (or_token[0] == '}') {
gi.Com_ErrorFmt("{}: Closing brace without data.\n", __FUNCTION__);
return false;
}
}

}
return true;
}

static void PrecacheForRandomRespawn() {
for (auto& item : itemList) {
if (!item.flags || (item.flags & (IF_NOT_GIVEABLE | IF_TECH | IF_NOT_RANDOM)) || !item.pickup || !item.worldModel)
Expand Down
64 changes: 64 additions & 0 deletions tests/test_entity_override_truncation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include <cassert>
#include <string>

#include "server/q_std.cpp"

local_game_import_t gi{};

static bool g_errorCalled = false;
static bool g_printCalled = false;

/*
=============
TestComPrint

Records when the print callback is invoked for validation.
=============
*/
static void TestComPrint(const char* message) {
g_printCalled = true;
(void)message;
}

/*
=============
TestComError

Records when the error callback is invoked for validation.
=============
*/
static void TestComError(const char* message) {
g_errorCalled = true;
(void)message;
}

/*
=============
main

Verifies that VerifyEntityString rejects truncated override data while
accepting a well-formed entity block.
=============
*/
int main() {
gi.Com_Print = TestComPrint;
gi.Com_Error = TestComError;

const char* valid = "{\n\"classname\" \"worldspawn\"\n}";
const char* truncated = "{\n\"classname\" \"worldspawn\"";
const char* empty = "";

g_errorCalled = false;
g_printCalled = false;
assert(VerifyEntityString(valid));
assert(!g_errorCalled);

g_errorCalled = false;
assert(!VerifyEntityString(truncated));
assert(g_errorCalled);

g_errorCalled = false;
assert(!VerifyEntityString(empty));
assert(g_errorCalled);
return 0;
}