From 4a50680807e9f70fc150065e2bd803044d913e55 Mon Sep 17 00:00:00 2001 From: Radegast Date: Sat, 17 Mar 2012 19:08:24 +0000 Subject: [PATCH] ported callvote fix from ioquake3, fixes #7 --- src/game/g_cmds.c | 18 +++++--- src/qcommon/cmd.c | 99 +++++++++++++++++++++++++++++++++++------- src/qcommon/qcommon.h | 2 + src/server/sv_client.c | 1 + 4 files changed, 99 insertions(+), 21 deletions(-) diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index a8859bccb..269c43ec8 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -2284,6 +2284,7 @@ Cmd_CallVote_f */ qboolean Cmd_CallVote_f(gentity_t *ent, unsigned int dwCommand, qboolean fRefCommand) { + char *c; int i; char arg1[MAX_STRING_TOKENS]; char arg2[MAX_STRING_TOKENS]; @@ -2325,15 +2326,20 @@ qboolean Cmd_CallVote_f(gentity_t *ent, unsigned int dwCommand, qboolean fRefCom trap_Argv(1, arg1, sizeof(arg1)); trap_Argv(2, arg2, sizeof(arg2)); - if (strchr(arg1, ';') || strchr(arg2, ';')) + // check for command separators in arg2 + for (c = arg2; *c; ++c) { - char *strCmdBase = (!fRefCommand) ? "vote" : "ref command"; - - G_refPrintf(ent, "Invalid %s string.", strCmdBase); - return(qfalse); + switch (*c) + { + case '\n': + case '\r': + case ';': + trap_SendServerCommand(ent - g_entities, "print \"Invalid vote string.\n\""); + return(qfalse); + break; + } } - if (trap_Argc() > 1 && (i = G_voteCmdCheck(ent, arg1, arg2, fRefCommand)) != G_NOTFOUND) // --OSP { if (i != G_OK) diff --git a/src/qcommon/cmd.c b/src/qcommon/cmd.c index fde864b68..0d562a89f 100644 --- a/src/qcommon/cmd.c +++ b/src/qcommon/cmd.c @@ -32,7 +32,7 @@ * @brief Quake script command processing module */ -#include "../qcommon/q_shared.h" +#include "q_shared.h" #include "qcommon.h" #define MAX_CMD_BUFFER 131072 @@ -69,7 +69,7 @@ void Cmd_Wait_f(void) } else { - cmd_wait = 1; + cmd_wait = 1; // ignore the argument } } @@ -112,7 +112,7 @@ void Cbuf_AddText(const char *text) Com_Printf("Cbuf_AddText: overflow\n"); return; } - memcpy(&cmd_text.data[cmd_text.cursize], text, l); + Com_Memcpy(&cmd_text.data[cmd_text.cursize], text, l); cmd_text.cursize += l; } @@ -144,7 +144,7 @@ void Cbuf_InsertText(const char *text) } // copy the new text in - memcpy(cmd_text.data, text, len - 1); + Com_Memcpy(cmd_text.data, text, len - 1); // add a \n cmd_text.data[len - 1] = '\n'; @@ -165,11 +165,13 @@ void Cbuf_ExecuteText(int exec_when, const char *text) case EXEC_NOW: if (text && strlen(text) > 0) { + Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", text); Cmd_ExecuteString(text); } else { Cbuf_Execute(); + Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", cmd_text.data); } break; case EXEC_INSERT: @@ -195,9 +197,14 @@ void Cbuf_Execute(void) char line[MAX_CMD_LINE]; int quotes; + // This will keep // style comments all on one line by not breaking on + // a semicolon. It will keep /* ... */ style comments all on one line by not + // breaking it for semicolon or newline. + qboolean in_star_comment = qfalse; + qboolean in_slash_comment = qfalse; while (cmd_text.cursize) { - if (cmd_wait) + if (cmd_wait > 0) { // skip out while text still remains in buffer, leaving it // for next frame @@ -205,7 +212,7 @@ void Cbuf_Execute(void) break; } - // find a \n or ; line break + // find a \n or ; line break or comment: // or /* */ text = (char *)cmd_text.data; quotes = 0; @@ -215,12 +222,37 @@ void Cbuf_Execute(void) { quotes++; } - if (!(quotes & 1) && text[i] == ';') + + if (!(quotes & 1)) { - break; // don't break if inside a quoted string + if (i < cmd_text.cursize - 1) + { + if (!in_star_comment && text[i] == '/' && text[i + 1] == '/') + { + in_slash_comment = qtrue; + } + else if (!in_slash_comment && text[i] == '/' && text[i + 1] == '*') + { + in_star_comment = qtrue; + } + else if (in_star_comment && text[i] == '*' && text[i + 1] == '/') + { + in_star_comment = qfalse; + // If we are in a star comment, then the part after it is valid + // Note: This will cause it to NUL out the terminating '/' + // but ExecuteString doesn't require it anyway. + i++; + break; + } + } + if (!in_slash_comment && !in_star_comment && text[i] == ';') + { + break; + } } - if (text[i] == '\n' || text[i] == '\r') + if (!in_star_comment && (text[i] == '\n' || text[i] == '\r')) { + in_slash_comment = qfalse; break; } } @@ -230,7 +262,7 @@ void Cbuf_Execute(void) i = MAX_CMD_LINE - 1; } - memcpy(line, text, i); + Com_Memcpy(line, text, i); line[i] = 0; // delete the text from the command buffer and move remaining commands down @@ -473,14 +505,41 @@ Cmd_Cmd Retrieve the unmodified command string For rcon use when you want to transmit without altering quoting -ATVI Wolfenstein Misc #284 ============ */ -char *Cmd_Cmd() +char *Cmd_Cmd(void) { return cmd_cmd; } +/* + * @brief Replaces command separators with space to prevent interpretation + * + * This is a hack to protect buggy qvms + * https://bugzilla.icculus.org/show_bug.cgi?id=3593 + * https://bugzilla.icculus.org/show_bug.cgi?id=4769 + */ +void Cmd_Args_Sanitize(void) +{ + int i; + + for (i = 1; i < cmd_argc; i++) + { + char *c = cmd_argv[i]; + + if (strlen(c) > MAX_CVAR_VALUE_STRING - 1) + { + c[MAX_CVAR_VALUE_STRING - 1] = '\0'; + } + + while ((c = strpbrk(c, "\n\r;"))) + { + *c = ' '; + ++c; + } + } +} + /* ============ Cmd_TokenizeString @@ -491,7 +550,7 @@ are inserted in the apropriate place, The argv array will point into this temporary buffer. ============ */ -void Cmd_TokenizeString(const char *text_in) +static void Cmd_TokenizeString2(const char *text_in, qboolean ignoreQuotes) { const char *text; char *textOut; @@ -558,7 +617,8 @@ void Cmd_TokenizeString(const char *text_in) } // handle quoted strings - if (*text == '"') + // NOTE: this doesn't handle \" escaping + if (!ignoreQuotes && *text == '"') { cmd_argv[cmd_argc] = textOut; cmd_argc++; @@ -583,7 +643,7 @@ void Cmd_TokenizeString(const char *text_in) // skip until whitespace, quote, or command while (*text > ' ') { - if (text[0] == '"') + if (!ignoreQuotes && text[0] == '"') { break; } @@ -616,6 +676,15 @@ void Cmd_TokenizeString(const char *text_in) } +void Cmd_TokenizeString(const char *text_in) +{ + Cmd_TokenizeString2(text_in, qfalse); +} + +void Cmd_TokenizeStringIgnoreQuotes(const char *text_in) +{ + Cmd_TokenizeString2(text_in, qtrue); +} /* ============ diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h index 7ed84bea8..3b79b6e54 100644 --- a/src/qcommon/qcommon.h +++ b/src/qcommon/qcommon.h @@ -485,11 +485,13 @@ char *Cmd_Args(void); char *Cmd_ArgsFrom(int arg); void Cmd_ArgsBuffer(char *buffer, int bufferLength); char *Cmd_Cmd(void); +void Cmd_Args_Sanitize(void); // The functions that execute commands get their parameters with these // functions. Cmd_Argv () will return an empty string, not a NULL // if arg > argc, so string operations are allways safe. void Cmd_TokenizeString(const char *text); +void Cmd_TokenizeStringIgnoreQuotes(const char *text_in); // Takes a null terminated string. Does not need to be /n terminated. // breaks the string up into arg tokens. diff --git a/src/server/sv_client.c b/src/server/sv_client.c index 8fe640db5..5ebc18663 100644 --- a/src/server/sv_client.c +++ b/src/server/sv_client.c @@ -1726,6 +1726,7 @@ void SV_ExecuteClientCommand(client_t *cl, const char *s, qboolean clientOK, qbo // pass unknown strings to the game if (!u->name && sv.state == SS_GAME) { + Cmd_Args_Sanitize(); VM_Call(gvm, GAME_CLIENT_COMMAND, cl - svs.clients); } }