diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 72e156792c..e2e8857a76 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,3 +1,12 @@ +December 14, 2007 +- Added "\c" support to ParseCommandLine() when it parses quoted strings. +- Fixed: When changing your name from the menu, you got an extra " appended + to your name if it ended with a backslash. +- Added escape sequences for user info strings, so now they can contain + embedded backslashes. +- Fixed an array-out-of-bounds access when drawing the player setup menu with + an invalid team number. + December 10, 2007 - Fixed: The MAPINFO flags that control jumping, crouching, and freelook, rather than overriding the dmflags values, actually overwrote the dmflags diff --git a/src/c_dispatch.cpp b/src/c_dispatch.cpp index eacaacad0a..e4e792f96f 100644 --- a/src/c_dispatch.cpp +++ b/src/c_dispatch.cpp @@ -718,8 +718,11 @@ void AddCommandString (char *cmd, int keynum) // to point to a buffer large enough to hold all the arguments. The // return value is the necessary size of this buffer. // -// Special processing: Inside quoted strings, \" becomes just " -// $ is replaced by the contents of +// Special processing: +// Inside quoted strings, \" becomes just " +// \\ becomes just \ +// \c becomes just TEXTCOLOR_ESCAPE +// $ is replaced by the contents of static long ParseCommandLine (const char *args, int *argc, char **argv) { @@ -759,6 +762,14 @@ static long ParseCommandLine (const char *args, int *argc, char **argv) { stuff = '\"', args++; } + else if (stuff == '\\' && *args == '\\') + { + args++; + } + else if (stuff == '\\' && *args == 'c') + { + stuff = TEXTCOLOR_ESCAPE, args++; + } else if (stuff == '\"') { stuff = 0; diff --git a/src/d_netinfo.cpp b/src/d_netinfo.cpp index cc1a6fb6ca..6b30d48a14 100644 --- a/src/d_netinfo.cpp +++ b/src/d_netinfo.cpp @@ -110,6 +110,55 @@ static const char *UserInfoStrings[] = NULL }; +// Replace \ with %/ and % with %% +FString D_EscapeUserInfo (const char *str) +{ + FString ret; + + for (; *str != '\0'; ++str) + { + if (*str == '\\') + { + ret << '%' << '/'; + } + else if (*str == '%') + { + ret << '%' << '%'; + } + else + { + ret << *str; + } + } + return ret; +} + +// Replace %/ with \ and %% with % +FString D_UnescapeUserInfo (const char *str, size_t len) +{ + const char *end = str + len; + FString ret; + + while (*str != '\0' && str < end) + { + if (*str == '%') + { + if (*(str + 1) == '/') + { + ret << '\\'; + str += 2; + continue; + } + else if (*(str + 1) == '%') + { + str++; + } + } + ret << *str++; + } + return ret; +} + int D_GenderToInt (const char *gender) { if (!stricmp (gender, "female")) @@ -339,6 +388,7 @@ void D_SetupUserInfo () void D_UserInfoChanged (FBaseCVar *cvar) { UCVarValue val; + FString escaped_val; char foo[256]; if (cvar == &autoaim) @@ -356,10 +406,11 @@ void D_UserInfoChanged (FBaseCVar *cvar) } val = cvar->GetGenericRep (CVAR_String); - if (4 + strlen (cvar->GetName ()) + strlen (val.String) > 256) + escaped_val = D_EscapeUserInfo(val.String); + if (4 + strlen(cvar->GetName()) + escaped_val.Len() > 256) I_Error ("User info descriptor too big"); - sprintf (foo, "\\%s\\%s", cvar->GetName (), val.String); + sprintf (foo, "\\%s\\%s", cvar->GetName(), escaped_val.GetChars()); Net_WriteByte (DEM_UINFCHANGED); Net_WriteString (foo); @@ -518,17 +569,18 @@ void D_WriteUserInfoStrings (int i, BYTE **stream, bool compact) "\\stillbob\\%g" "\\playerclass\\%s" , - info->netname, + D_EscapeUserInfo(info->netname).GetChars(), (double)info->aimdist / (float)ANGLE_1, RPART(info->color), GPART(info->color), BPART(info->color), - skins[info->skin].name, info->team, + D_EscapeUserInfo(skins[info->skin].name).GetChars(), + info->team, info->gender == GENDER_FEMALE ? "female" : info->gender == GENDER_NEUTER ? "other" : "male", info->neverswitch, (float)(info->MoveBob) / 65536.f, (float)(info->StillBob) / 65536.f, info->PlayerClass == -1 ? "Random" : - type->Meta.GetMetaString (APMETA_DisplayName) + D_EscapeUserInfo(type->Meta.GetMetaString (APMETA_DisplayName)).GetChars() ); } else @@ -546,10 +598,10 @@ void D_WriteUserInfoStrings (int i, BYTE **stream, bool compact) "\\%g" // stillbob "\\%s" // playerclass , - info->netname, + D_EscapeUserInfo(info->netname).GetChars(), (double)info->aimdist / (float)ANGLE_1, RPART(info->color), GPART(info->color), BPART(info->color), - skins[info->skin].name, + D_EscapeUserInfo(skins[info->skin].name).GetChars(), info->team, info->gender == GENDER_FEMALE ? "female" : info->gender == GENDER_NEUTER ? "other" : "male", @@ -557,7 +609,7 @@ void D_WriteUserInfoStrings (int i, BYTE **stream, bool compact) (float)(info->MoveBob) / 65536.f, (float)(info->StillBob) / 65536.f, info->PlayerClass == -1 ? "Random" : - type->Meta.GetMetaString (APMETA_DisplayName) + D_EscapeUserInfo(type->Meta.GetMetaString (APMETA_DisplayName)).GetChars() ); } } @@ -568,9 +620,9 @@ void D_WriteUserInfoStrings (int i, BYTE **stream, bool compact) void D_ReadUserInfoStrings (int i, BYTE **stream, bool update) { userinfo_t *info = &players[i].userinfo; - char *ptr = *((char **)stream); - char *breakpt; - char *value; + const char *ptr = *((const char **)stream); + const char *breakpt; + FString value; bool compact; int infotype = -1; @@ -583,25 +635,37 @@ void D_ReadUserInfoStrings (int i, BYTE **stream, bool update) { for (;;) { - breakpt = strchr (ptr, '\\'); + int j; - if (breakpt != NULL) - *breakpt = 0; + breakpt = strchr (ptr, '\\'); if (compact) { - value = ptr; + value = D_UnescapeUserInfo(ptr, breakpt - ptr); infotype++; } - else if (breakpt != NULL) + else { - value = breakpt + 1; - if ( (breakpt = strchr (value, '\\')) ) - *breakpt = 0; + assert(breakpt != NULL); + // A malicious remote machine could invalidate the above assert. + if (breakpt == NULL) + { + break; + } + const char *valstart = breakpt + 1; + if ( (breakpt = strchr (valstart, '\\')) != NULL ) + { + value = D_UnescapeUserInfo(valstart, breakpt - valstart); + } + else + { + value = D_UnescapeUserInfo(valstart, strlen(valstart)); + } - int j = 0; - while (UserInfoStrings[j] && stricmp (UserInfoStrings[j], ptr) != 0) - j++; + for (j = 0; + UserInfoStrings[j] && strnicmp (UserInfoStrings[j], ptr, valstart - ptr - 1) != 0; + ++j) + { } if (UserInfoStrings[j] == NULL) { infotype = -1; @@ -611,10 +675,6 @@ void D_ReadUserInfoStrings (int i, BYTE **stream, bool update) infotype = j; } } - else - { // Shush, GCC. - value = NULL; - } switch (infotype) { @@ -718,13 +778,8 @@ void D_ReadUserInfoStrings (int i, BYTE **stream, bool update) break; } - if (!compact) - { - *(value - 1) = '\\'; - } if (breakpt) { - *breakpt = '\\'; ptr = breakpt + 1; } else diff --git a/src/m_menu.cpp b/src/m_menu.cpp index 38e46fa076..d46baed748 100644 --- a/src/m_menu.cpp +++ b/src/m_menu.cpp @@ -2078,7 +2078,7 @@ static void M_PlayerSetupDrawer () screen->DrawText (label, PSetupDef.x, PSetupDef.y + LINEHEIGHT+yo, "Team", DTA_Clean, true, TAG_DONE); screen->DrawText (value, x, PSetupDef.y + LINEHEIGHT+yo, - players[consoleplayer].userinfo.team == TEAM_None ? "None" : + (unsigned)players[consoleplayer].userinfo.team >= NUM_TEAMS ? "None" : TeamNames[players[consoleplayer].userinfo.team], DTA_Clean, true, TAG_DONE); @@ -2279,6 +2279,7 @@ static BYTE smoke[1024] = 7, 7, 0, 5, 1, 6, 7, 9,12, 9,12,21,22,25,24,22,23,25,24,18,24,22,17,13,10, 9,10, 9, 6,11, 6, 5, }; +// This is one plasma and two rotozoomers. I think it turned out quite awesome. static void M_RenderPlayerBackdrop () { BYTE *from; @@ -2521,9 +2522,19 @@ static void M_PlayerNameNotChanged () static void M_PlayerNameChanged (FSaveGameNode *dummy) { - char command[SAVESTRINGSIZE+8]; + const char *p; + FString command("name \""); - sprintf (command, "name \"%s\"", savegamestring); + // Escape any backslashes or quotation marks before sending the name to the console. + for (p = savegamestring; *p != '\0'; ++p) + { + if (*p == '"' || *p == '\\') + { + command << '\\'; + } + command << *p; + } + command << '"'; C_DoCommand (command); } diff --git a/src/sc_man_scanner.h b/src/sc_man_scanner.h index faf242f36b..658f16f740 100644 --- a/src/sc_man_scanner.h +++ b/src/sc_man_scanner.h @@ -4478,7 +4478,7 @@ newline: normal_token: ScriptPtr = (YYCURSOR >= YYLIMIT) ? ScriptEndPtr : cursor; - sc_StringLen = MIN (ScriptPtr - tok, MAX_STRING_SIZE-1); + sc_StringLen = (unsigned int)MIN (ScriptPtr - tok, MAX_STRING_SIZE-1); if (tokens && (sc_TokenType == TK_StringConst || sc_TokenType == TK_NameConst)) { sc_StringLen -= 2;