2
0
mirror of https://github.com/rehlds/rehlds.git synced 2025-01-14 15:48:04 +03:00

Info code refactoring (#604)

* info.cpp refactoring
* Update info unittests
* Make _vgui_menus important and fix tests passing
This commit is contained in:
theAsmodai 2018-05-04 23:37:53 +03:00 committed by GitHub
parent 97868baf92
commit f324df867f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 758 additions and 162 deletions

View File

@ -52,7 +52,7 @@ Bugfixed version of rehlds contains an additional cvars:
<li>sv_rehlds_stringcmdrate_avg_punish // Time in minutes for which the player will be banned (0 - Permanent, use a negative number for a kick). Default: 5
<li>sv_rehlds_stringcmdrate_max_burst // Max burst level of 'string' cmds for ban. Default: 400
<li>sv_rehlds_stringcmdrate_burst_punish // Time in minutes for which the player will be banned (0 - Permanent, use a negative number for a kick). Default: 5
<li>sv_rehlds_userinfo_transmitted_fields // Userinfo fields only with these keys will be transmitted to clients via network. If not set then all fields will be transmitted (except prefixed with underscore). Each key must be prefixed by backslash, for example "\name\model\*sid\*hltv\bottomcolor\topcolor". Default: ""
<li>sv_rehlds_userinfo_transmitted_fields // Userinfo fields only with these keys will be transmitted to clients via network. If not set then all fields will be transmitted (except prefixed with underscore). Each key must be prefixed by backslash, for example "\name\model\*sid\*hltv\bottomcolor\topcolor". See [wiki](https://github.com/dreamstalker/rehlds/wiki/Userinfo-keys) to collect sufficient set of keys for your server. Default: ""
<li>sv_rehlds_attachedentities_playeranimationspeed_fix // Fixes bug with gait animation speed increase when player has some attached entities (aiments). Can cause animation lags when cl_updaterate is low. Default: 0
</ul>

View File

@ -81,7 +81,7 @@ bool InfoString::SetString(char *string)
return false;
}
Q_strnlcpy(m_String, string, m_MaxSize);
Q_strlcpy(m_String, string, m_MaxSize);
return true;
}
@ -95,7 +95,7 @@ void InfoString::SetMaxSize(unsigned int maxSize)
if (m_String)
{
if (maxSize > Q_strlen(m_String)) {
Q_strnlcpy(newBuffer, m_String, maxSize);
Q_strlcpy(newBuffer, m_String, maxSize);
}
Mem_Free(m_String);

View File

@ -105,7 +105,7 @@
extern char g_szEXEName[ 4096 ];
#define _snprintf snprintf
#if defined(OSX)
#define SO_ARCH_SUFFIX ".dylib"
#else

View File

@ -2365,17 +2365,56 @@ void Host_Version_f(void)
void Host_FullInfo_f(void)
{
char key[512];
char value[512];
char *o;
char *s;
if (Cmd_Argc() != 2)
{
Con_Printf("fullinfo <complete info string>\n");
return;
}
#ifdef REHLDS_FIXES
char copy[MAX_INFO_STRING];
Q_strlcpy(copy, Cmd_Argv(1));
char* s = copy;
if (*s != '\\')
return;
bool eos = false;
while (!eos) {
const char* key = ++s;
// key
while (*s != '\\')
{
// key should end with a '\', not a NULL
if (*s == '\0') {
Con_Printf("MISSING VALUE\n");
return;
}
s++;
}
*s = '\0';
const char* value = ++s;
// value
while (*s != '\\') {
if (*s == '\0') {
eos = true;
break;
}
s++;
}
*s = '\0';
#else
char key[512];
char value[512];
char *o;
char *s;
s = (char *)Cmd_Argv(1);
if (*s == '\\')
s++;
@ -2400,6 +2439,7 @@ void Host_FullInfo_f(void)
*o = 0;
if (*s)
s++;
#endif
if (cmd_source == src_command)
{

View File

@ -31,10 +31,84 @@
// NOTE: This file contains a lot of fixes that are not covered by REHLDS_FIXES define.
// TODO: Most of the Info_ functions can be speedup via removing unneded copy of key and values.
struct info_field_t
{
char* name;
bool integer;
};
info_field_t g_info_important_fields[] =
{
// name integer
{ "name", false },
{ "model", false },
// model colors
{ "topcolor", true },
{ "bottomcolor", true },
// network
{ "rate", true },
{ "cl_updaterate", true },
{ "cl_lw", true },
{ "cl_lc", true },
// hltv flag
{ "*hltv", true },
// avatars
{ "*sid", false }, // transmit as string because it's int64
// gui/text menus
{ "_vgui_menus", true }
};
std::vector<info_field_t *> g_info_transmitted_fields;
// Searches the string for the given
// key and returns the associated value, or an empty string.
const char* EXT_FUNC Info_ValueForKey(const char *s, const char *key)
const char* EXT_FUNC Info_ValueForKey(const char *s, const char *lookup)
{
#ifdef REHLDS_FIXES
static char valueBuf[INFO_MAX_BUFFER_VALUES][MAX_KV_LEN];
static int valueIndex;
while (*s == '\\')
{
// skip starting slash
const char* key = ++s;
// skip key
while (*s != '\\') {
// Add some sanity checks because it's external function
if (*s == '\0')
return "";
s++;
}
size_t keyLen = s - key;
const char* value = ++s; // skip separating slash
// skip value
while (*s != '\\' && *s != '\0')
s++;
size_t valueLen = Q_min(s - value, MAX_KV_LEN - 1);
if (!Q_strncmp(key, lookup, keyLen))
{
char* dest = valueBuf[valueIndex];
Q_memcpy(dest, value, valueLen);
dest[valueLen] = '\0';
valueIndex = (valueIndex + 1) % INFO_MAX_BUFFER_VALUES;
return dest;
}
}
return "";
#else
// use few (two?) buffers so compares work without stomping on each other
static char value[INFO_MAX_BUFFER_VALUES][MAX_KV_LEN];
static int valueindex;
@ -69,7 +143,7 @@ const char* EXT_FUNC Info_ValueForKey(const char *s, const char *key)
*c = 0;
s++; // skip the slash
// Copy a value
// Copy a value
nCount = 0;
c = value[valueindex];
while (*s != '\\')
@ -88,7 +162,7 @@ const char* EXT_FUNC Info_ValueForKey(const char *s, const char *key)
}
*c = 0;
if (!Q_strcmp(key, pkey))
if (!Q_strcmp(lookup, pkey))
{
c = value[valueindex];
valueindex = (valueindex + 1) % INFO_MAX_BUFFER_VALUES;
@ -97,10 +171,47 @@ const char* EXT_FUNC Info_ValueForKey(const char *s, const char *key)
}
return "";
#endif
}
void Info_RemoveKey(char *s, const char *key)
void Info_RemoveKey(char *s, const char *lookup)
{
#ifdef REHLDS_FIXES
size_t lookupLen = Q_strlen(lookup);
while (*s == '\\')
{
char* start = s;
// skip starting slash
const char* key = ++s;
// skip key
while (*s != '\\') {
if (*s == '\0')
return;
s++;
}
size_t keyLen = s - key;
++s; // skip separating slash
// skip value
while (*s != '\\' && *s != '\0')
s++;
if (keyLen != lookupLen)
continue;
if (!Q_memcmp(key, lookup, lookupLen))
{
// cut key and value
Q_memmove(start, s, Q_strlen(s) + 1);
break;
}
}
#else
char pkey[MAX_KV_LEN];
char value[MAX_KV_LEN];
char *start;
@ -108,13 +219,13 @@ void Info_RemoveKey(char *s, const char *key)
int cmpsize;
int nCount;
if (Q_strstr(key, "\\"))
if (Q_strstr(lookup, "\\"))
{
Con_Printf("Can't use a key with a \\\n");
return;
}
cmpsize = Q_strlen(key);
cmpsize = Q_strlen(lookup);
if (cmpsize > MAX_KV_LEN - 1)
cmpsize = MAX_KV_LEN - 1;
@ -168,16 +279,47 @@ void Info_RemoveKey(char *s, const char *key)
*c = 0;
// Compare keys
if (!Q_strncmp(key, pkey, cmpsize))
if (!Q_strncmp(lookup, pkey, cmpsize))
{
Q_strcpy_s(start, s); // remove this part
s = start; // continue searching
}
}
#endif
}
void Info_RemovePrefixedKeys(char *s, const char prefix)
{
#ifdef REHLDS_FIXES
while (*s == '\\')
{
char* start = s;
// skip starting slash
const char* key = ++s;
// skip key
while (*s != '\\') {
if (*s == '\0')
return;
s++;
}
// skip separating slash
++s;
// skip value
while (*s != '\\' && *s != '\0')
s++;
if (key[0] == prefix)
{
Q_memmove(start, s, Q_strlen(s) + 1);
s = start;
}
}
#else
char pkey[MAX_KV_LEN];
char value[MAX_KV_LEN];
char *start;
@ -239,12 +381,37 @@ void Info_RemovePrefixedKeys(char *s, const char prefix)
s = start; // continue searching
}
}
#endif
}
#ifdef REHLDS_FIXES
qboolean Info_IsKeyImportant(const char *key)
{
if (key[0] == '*')
return true;
for (auto& field : g_info_important_fields) {
if (!Q_strcmp(key, field.name))
return true;
}
return false;
}
qboolean Info_IsKeyImportant(const char *key, size_t keyLen)
{
char copy[MAX_KV_LEN];
keyLen = min(keyLen, sizeof(copy) - 1);
Q_memcpy(copy, key, keyLen);
copy[keyLen] = '\0';
return Info_IsKeyImportant(copy);
}
#else
qboolean Info_IsKeyImportant(const char *key)
{
if (key[0] == '*')
return true;
if (!Q_strcmp(key, "name"))
return true;
if (!Q_strcmp(key, "model"))
@ -261,19 +428,53 @@ qboolean Info_IsKeyImportant(const char *key)
return true;
if (!Q_strcmp(key, "cl_lc"))
return true;
#ifndef REHLDS_FIXES
// keys starts from '*' already checked
if (!Q_strcmp(key, "*hltv"))
return true;
if (!Q_strcmp(key, "*sid"))
return true;
#endif
return false;
}
#endif
char *Info_FindLargestKey(char *s, int maxsize)
const char *Info_FindLargestKey(const char *s, int maxsize)
{
#ifdef REHLDS_FIXES
static char largestKey[MAX_KV_LEN];
size_t largestLen = 0;
while (*s == '\\')
{
// skip starting slash
const char* key = ++s;
// skip key
while (*s != '\\') {
if (*s == '\0')
return "";
s++;
}
size_t keyLen = s - key;
const char* value = ++s; // skip separating slash
// skip value
while (*s != '\\' && *s != '\0')
s++;
size_t valueLen = s - value;
size_t totalLen = keyLen + valueLen;
if (totalLen > largestLen && !Info_IsKeyImportant(key, keyLen)) {
largestLen = totalLen;
Q_memcpy(largestKey, key, keyLen);
largestKey[keyLen] = '\0';
}
}
return largestLen ? largestKey : "";
#else
static char largest_key[MAX_KV_LEN];
char key[MAX_KV_LEN];
char value[MAX_KV_LEN];
@ -347,8 +548,104 @@ char *Info_FindLargestKey(char *s, int maxsize)
}
return largest_key;
#endif
}
#ifdef REHLDS_FIXES
qboolean Info_SetValueForStarKey(char *s, const char *key, const char *value, size_t maxsize)
{
char newArray[MAX_INFO_STRING], valueBuf[MAX_KV_LEN];
if (!key || !value)
{
Con_Printf("Keys and values can't be null\n");
return FALSE;
}
if (key[0] == '\0')
{
Con_Printf("Keys can't be an empty string\n");
return FALSE;
}
if (Q_strchr(key, '\\') || Q_strchr(value, '\\'))
{
Con_Printf("Can't use keys or values with a \\\n");
return FALSE;
}
if (Q_strchr(key, '\"') || Q_strchr(value, '\"'))
{
Con_Printf("Can't use keys or values with a \"\n");
return FALSE;
}
if (Q_strstr(key, "..") || Q_strstr(value, ".."))
{
Con_Printf("Can't use keys or values with a ..\n");
return FALSE;
}
int keyLen = Q_strlen(key);
int valueLen = Q_strlen(value);
if (keyLen >= MAX_KV_LEN || valueLen >= MAX_KV_LEN)
{
Con_Printf("Keys and values must be < %i characters\n", MAX_KV_LEN);
return FALSE;
}
if (!Q_UnicodeValidate(key) || !Q_UnicodeValidate(value))
{
Con_Printf("Keys and values must be valid utf8 text\n");
return FALSE;
}
// Remove current key/value and return if we doesn't specified to set a value
Info_RemoveKey(s, key);
if (value[0] == '\0')
{
return TRUE;
}
// auto lowercase team
if (!Q_strcmp(key, "team")) {
value = Q_strcpy(valueBuf, value);
Q_strlwr(valueBuf);
}
// Create key/value pair
size_t neededLength = Q_snprintf(newArray, sizeof newArray, "\\%s\\%s", key, value);
if (Q_strlen(s) + neededLength >= maxsize)
{
// no more room in the buffer to add key/value
if (!Info_IsKeyImportant(key))
{
// no room to add setting
Con_Printf("Info string length exceeded\n");
return FALSE;
}
// keep removing the largest key/values until we have a room
do
{
const char* largekey = Info_FindLargestKey(s, maxsize);
if (largekey[0] == '\0')
{
// no room to add setting
Con_Printf("Info string length exceeded\n");
return FALSE;
}
Info_RemoveKey(s, largekey);
} while ((int)Q_strlen(s) + neededLength >= maxsize);
}
Q_strcat(s, newArray);
return TRUE;
}
#else
void Info_SetValueForStarKey(char *s, const char *key, const char *value, int maxsize)
{
char newArray[MAX_INFO_STRING];
@ -424,7 +721,7 @@ void Info_SetValueForStarKey(char *s, const char *key, const char *value, int ma
}
// keep removing the largest key/values until we have a room
char *largekey;
const char *largekey;
do
{
largekey = Info_FindLargestKey(s, maxsize);
@ -453,6 +750,7 @@ void Info_SetValueForStarKey(char *s, const char *key, const char *value, int ma
}
*s = 0;
}
#endif
void Info_SetValueForKey(char *s, const char *key, const char *value, int maxsize)
{
@ -541,6 +839,89 @@ void Info_Print(const char *s)
qboolean Info_IsValid(const char *s)
{
#ifdef REHLDS_FIXES
struct {
const char* start;
size_t len;
} existingKeys[MAX_INFO_STRING * 2 / 4];
size_t existingKeysNum = 0;
auto isAlreadyExists = [&](const char* key, size_t len)
{
for (size_t i = 0; i < existingKeysNum; i++) {
if (len == existingKeys[i].len && !Q_memcmp(key, existingKeys[i].start, existingKeys[i].len))
return true;
}
return false;
};
while (*s == '\\')
{
const char* key = ++s;
// keys and values are separated by another slash
while (*s != '\\')
{
// key should end with a '\', not a NULL
if (*s == '\0')
return FALSE;
// quotes are deprecated
if (*s == '"')
return FALSE;
// ".." deprecated. don't know why. model path?
if (*s == '.' && *(s + 1) == '.')
return FALSE;
s++;
}
size_t keyLen = s - key;
if (keyLen == 0 || keyLen >= MAX_KV_LEN)
return FALSE;
if (isAlreadyExists(key, keyLen))
return FALSE;
const char* value = ++s; // skip the slash
// values should be ended by eos or slash
while (*s != '\\' && *s != '\0')
{
// quotes are deprecated
if (*s == '"')
return FALSE;
// ".." deprecated
if (*s == '.' && *(s + 1) == '.')
return FALSE;
s++;
}
size_t valueLen = s - value;
if (valueLen == 0 || valueLen >= MAX_KV_LEN)
return FALSE;
if (*s == '\0')
return TRUE;
if (existingKeysNum == ARRAYSIZE(existingKeys))
return FALSE;
existingKeys[existingKeysNum].start = key;
existingKeys[existingKeysNum].len = keyLen;
existingKeysNum++;
}
return FALSE;
#else
char key[MAX_KV_LEN];
char value[MAX_KV_LEN];
char *c;
int nCount;
while (*s)
{
if (*s == '\\')
@ -548,48 +929,46 @@ qboolean Info_IsValid(const char *s)
s++; // skip the slash
}
// Returns character count
// -1 - error
// 0 - string size is zero
enum class AllowNull {
Yes,
No,
};
auto validate = [&s](AllowNull allowNull) -> int
// Copy a key
nCount = 0;
c = key;
while (*s != '\\')
{
int nCount = 0;
for(; *s != '\\'; nCount++, s++)
if (!*s)
{
if (!*s)
{
return (allowNull == AllowNull::Yes) ? nCount : -1;
}
if (nCount >= MAX_KV_LEN)
{
return -1; // string length should be less then MAX_KV_LEN
}
#ifdef REHLDS_FIXES
if (*s == '\"')
{
return -1; // string should not contain "
}
#endif
return FALSE; // key should end with a \, not a NULL
}
return nCount;
};
if (validate(AllowNull::No) == -1)
{
return FALSE;
if (nCount >= MAX_KV_LEN)
{
return FALSE; // key length should be less then MAX_KV_LEN
}
*c++ = *s++;
nCount++;
}
s++; // Skip slash
*c = 0;
s++; // skip the slash
if (validate(AllowNull::Yes) <= 0)
// Copy a value
nCount = 0;
c = value;
while (*s != '\\')
{
return FALSE;
if (!*s)
{
break; // allow value to be ended with NULL
}
if (nCount >= MAX_KV_LEN)
{
return FALSE; // value length should be less then MAX_KV_LEN
}
*c++ = *s++;
nCount++;
}
*c = 0;
if (value[0] == 0)
{
return FALSE; // empty values are not valid
}
if (!*s)
@ -599,52 +978,89 @@ qboolean Info_IsValid(const char *s)
}
return FALSE;
#endif
}
#ifdef REHLDS_FIXES
void Info_CollectFields(char *destInfo, const char *srcInfo, const char *collectedKeysOfFields)
void Info_SetFieldsToTransmit()
{
char keys[MAX_INFO_STRING];
Q_strcpy(keys, collectedKeysOfFields);
// clean all
for (auto field : g_info_transmitted_fields) {
free(field->name);
delete field;
}
g_info_transmitted_fields.clear();
size_t userInfoLength = 0;
for (const char *key = strtok(keys, "\\"); key; key = strtok(nullptr, "\\"))
char keys[512];
Q_strlcpy(keys, sv_rehlds_userinfo_transmitted_fields.string);
auto isIntegerField = [](const char* key)
{
const char *value = Info_ValueForKey(srcInfo, key);
for (auto& x : g_info_important_fields) {
if (!Q_strcmp(key, x.name))
return x.integer;
}
return false;
};
for (char *key = Q_strtok(keys, "\\"); key; key = Q_strtok(nullptr, "\\"))
{
if (key[0] == '_') {
Con_Printf("%s: private key '%s' couldn't be transmitted.\n", __FUNCTION__, key);
continue;
}
if (Q_strlen(key) >= MAX_KV_LEN) {
Con_Printf("%s: key '%s' is too long (should be < %i characters)\n", __FUNCTION__, key, MAX_KV_LEN);
continue;
}
if (std::find_if(g_info_transmitted_fields.begin(), g_info_transmitted_fields.end(), [key](info_field_t* field) { return !Q_strcmp(key, field->name); }) == g_info_transmitted_fields.end()) {
auto field = new info_field_t;
field->name = Q_strdup(key);
field->integer = isIntegerField(key);
g_info_transmitted_fields.push_back(field);
}
}
}
void Info_CollectFields(char *destInfo, const char *srcInfo, size_t maxsize)
{
if (g_info_transmitted_fields.empty()) {
Q_strlcpy(destInfo, srcInfo, maxsize);
Info_RemovePrefixedKeys(destInfo, '_');
return;
}
char add[512], valueBuf[32];
size_t userInfoLength = 0;
for (auto field : g_info_transmitted_fields)
{
const char *value = Info_ValueForKey(srcInfo, field->name);
if (value[0] == '\0')
continue;
// Integer fields
if (!Q_strcmp(key, "*hltv")
|| !Q_strcmp(key, "bottomcolor")
|| !Q_strcmp(key, "topcolor"))
// clean garbage from integer fields
if (field->integer)
{
// don't send zero fields
int intValue = Q_atoi(value);
if (!intValue)
continue;
destInfo[userInfoLength++] = '\\';
Q_strcpy(&destInfo[userInfoLength], key);
userInfoLength += Q_strlen(key);
destInfo[userInfoLength++] = '\\';
userInfoLength += Q_sprintf(&destInfo[userInfoLength], "%d", intValue);
}
// String fields
else
{
destInfo[userInfoLength++] = '\\';
Q_strcpy(&destInfo[userInfoLength], key);
userInfoLength += Q_strlen(key);
destInfo[userInfoLength++] = '\\';
Q_strcpy(&destInfo[userInfoLength], value);
userInfoLength += Q_strlen(value);
Q_sprintf(valueBuf, "%i", intValue);
value = valueBuf;
}
// don't write truncated keys/values
size_t len = Q_sprintf(add, "\\%s\\%s", field->name, value);
if (userInfoLength + len < maxsize) {
Q_strcpy(destInfo + userInfoLength, add);
userInfoLength += len;
}
}
destInfo[userInfoLength] = '\0';
}
#endif // REHLDS_FIXES

View File

@ -48,11 +48,16 @@ const char *Info_ValueForKey(const char *s, const char *key);
void Info_RemoveKey(char *s, const char *key);
void Info_RemovePrefixedKeys(char *s, const char prefix);
qboolean Info_IsKeyImportant(const char *key);
char *Info_FindLargestKey(char *s, int maxsize);
const char *Info_FindLargestKey(const char *s, int maxsize);
#ifdef REHLDS_FIXES
qboolean Info_SetValueForStarKey(char *s, const char *key, const char *value, size_t maxsize);
#else
void Info_SetValueForStarKey(char *s, const char *key, const char *value, int maxsize);
#endif
void Info_SetValueForKey(char *s, const char *key, const char *value, int maxsize);
void Info_Print(const char *s);
qboolean Info_IsValid(const char *s);
#ifdef REHLDS_FIXES
void Info_CollectFields(char *destInfo, const char *srcInfo, const char *collectedKeysOfFields);
void Info_SetFieldsToTransmit();
void Info_CollectFields(char *destInfo, const char *srcInfo, size_t maxsize);
#endif

View File

@ -1927,7 +1927,7 @@ int EXT_FUNC SV_CheckKeyInfo_internal(netadr_t *adr, char *protinfo, unsigned sh
s = Info_ValueForKey(protinfo, "raw");
if (s[0] == 0 || (nAuthProtocol == 2 && Q_strlen(s) != 32))
if (s[0] == '\0' || (nAuthProtocol == 2 && Q_strlen(s) != 32))
{
SV_RejectConnection(adr, "Invalid authentication certificate length.\n");
return 0;
@ -2051,7 +2051,6 @@ int SV_CheckUserInfo(netadr_t *adr, char *userinfo, qboolean bIsReconnecting, in
const char *s;
char newname[MAX_NAME];
int proxies;
int i;
if (!NET_IsLocalAddress(*adr))
{
@ -2076,13 +2075,15 @@ int SV_CheckUserInfo(netadr_t *adr, char *userinfo, qboolean bIsReconnecting, in
}
}
i = Q_strlen(userinfo);
#ifndef REHLDS_FIXES
int i = Q_strlen(userinfo);
if (i <= 4 || Q_strstr(userinfo, "\\\\") || userinfo[i - 1] == '\\')
{
SV_RejectConnection(adr, "Unknown HLTV client type.\n");
return 0;
}
#endif
Info_RemoveKey(userinfo, "password");
@ -2122,9 +2123,9 @@ int SV_CheckUserInfo(netadr_t *adr, char *userinfo, qboolean bIsReconnecting, in
#endif
#ifdef REHLDS_FIXES
if (name[0] == 0 || !Q_stricmp(name, "console") || Q_strstr(name, "..") || Q_strstr(name, "\"") || Q_strstr(name, "\\"))
if (name[0] == '\0' || !Q_stricmp(name, "console"))
#else // REHLDS_FIXES
if (name[0] == 0 || !Q_stricmp(name, "console") || Q_strstr(name, "..") != NULL)
if (name[0] == '\0' || !Q_stricmp(name, "console") || Q_strstr(name, "..") != NULL)
#endif // REHLDS_FIXES
{
Info_SetValueForKey(userinfo, "name", "unnamed", MAX_INFO_STRING);
@ -2137,11 +2138,10 @@ int SV_CheckUserInfo(netadr_t *adr, char *userinfo, qboolean bIsReconnecting, in
if (SV_CheckForDuplicateNames(userinfo, bIsReconnecting, nReconnectSlot))
{
Q_strncpy(name, Info_ValueForKey(userinfo, "name"), MAX_NAME - 1);
name[MAX_NAME - 1] = 0;
name[MAX_NAME - 1] = '\0';
}
s = Info_ValueForKey(userinfo, "*hltv");
if (!s[0])
return 1;
@ -3750,17 +3750,12 @@ void SV_FullClientUpdate(client_t *cl, sizebuf_t *sb)
char info[MAX_INFO_STRING];
#ifdef REHLDS_FIXES
if (sv_rehlds_userinfo_transmitted_fields.string[0] != '\0')
{
Info_CollectFields(info, cl->userinfo, sv_rehlds_userinfo_transmitted_fields.string);
}
else
Info_CollectFields(info, cl->userinfo, MAX_INFO_STRING);
#else // REHLDS_FIXES
Q_strncpy(info, cl->userinfo, sizeof(info) - 1);
info[sizeof(info) - 1] = '\0';
Info_RemovePrefixedKeys(info, '_');
#endif // REHLDS_FIXES
{
Q_strncpy(info, cl->userinfo, sizeof(info) - 1);
info[sizeof(info) - 1] = 0;
Info_RemovePrefixedKeys(info, '_');
}
g_RehldsHookchains.m_SV_WriteFullClientUpdate.callChain(SV_WriteFullClientUpdate_internal, GetRehldsApiClient(cl), info, MAX_INFO_STRING, sb, GetRehldsApiClient((sb == &g_psv.reliable_datagram) ? nullptr : host_client));
}
@ -4904,12 +4899,7 @@ void SV_ExtractFromUserinfo(client_t *cl)
Q_UnicodeRepair(newname);
}
if (newname[0] == '\0' || !Q_stricmp(newname, "console")
#ifdef REHLDS_FIXES
|| Q_strstr(newname, "..") || Q_strstr(newname, "\"") || Q_strstr(newname, "\\"))
#else // REHLDS_FIXES
)
#endif // REHLDS_FIXES
if (newname[0] == '\0' || !Q_stricmp(newname, "console"))
{
Info_SetValueForKey(userinfo, "name", "unnamed", MAX_INFO_STRING);
}
@ -4930,26 +4920,26 @@ void SV_ExtractFromUserinfo(client_t *cl)
ISteamGameServer_BUpdateUserData(cl->network_userid.m_SteamID, cl->name, 0);
val = Info_ValueForKey(userinfo, "rate");
if (val[0] != 0)
if (val[0] != '\0')
{
i = Q_atoi(val);
cl->netchan.rate = Q_clamp(float(i), MIN_RATE, MAX_RATE);
}
val = Info_ValueForKey(userinfo, "topcolor");
if (val[0] != 0)
if (val[0] != '\0')
cl->topcolor = Q_atoi(val);
else
Con_DPrintf("topcolor unchanged for %s\n", cl->name);
val = Info_ValueForKey(userinfo, "bottomcolor");
if (val[0] != 0)
if (val[0] != '\0')
cl->bottomcolor = Q_atoi(val);
else
Con_DPrintf("bottomcolor unchanged for %s\n", cl->name);
val = Info_ValueForKey(userinfo, "cl_updaterate");
if (val[0] != 0)
if (val[0] != '\0')
{
i = Q_atoi(val);
if (i >= 10)
@ -4959,13 +4949,13 @@ void SV_ExtractFromUserinfo(client_t *cl)
}
val = Info_ValueForKey(userinfo, "cl_lw");
cl->lw = val[0] != 0 ? Q_atoi(val) != 0 : 0;
cl->lw = val[0] != '\0' ? Q_atoi(val) != 0 : 0;
val = Info_ValueForKey(userinfo, "cl_lc");
cl->lc = val[0] != 0 ? Q_atoi(val) != 0 : 0;
cl->lc = val[0] != '\0' ? Q_atoi(val) != 0 : 0;
val = Info_ValueForKey(userinfo, "*hltv");
cl->proxy = val[0] != 0 ? Q_atoi(val) == TYPE_PROXY : 0;
cl->proxy = val[0] != '\0' ? Q_atoi(val) == TYPE_PROXY : 0;
SV_CheckUpdateRate(&cl->next_messageinterval);
SV_CheckRate(cl);
@ -5803,6 +5793,10 @@ void EXT_FUNC SV_ActivateServer_internal(int runPhysics)
Q_sprintf(szCommand, "exec %s\n", mapchangecfgfile.string);
Cbuf_AddText(szCommand);
}
#ifdef REHLDS_FIXES
Info_SetFieldsToTransmit();
#endif
}
void SV_ServerShutdown(void)

View File

@ -80,6 +80,7 @@ inline char *_strlwr(char *start)
#define Q_strstr A_strstr
#define Q_strchr strchr
#define Q_strrchr strrchr
#define Q_strtok strtok
#define Q_strlwr A_strtolower
#define Q_strupr A_strtoupper
#define Q_sprintf sprintf
@ -120,6 +121,7 @@ inline char *_strlwr(char *start)
#define Q_strstr strstr
#define Q_strchr strchr
#define Q_strrchr strrchr
#define Q_strtok strtok
#define Q_strlwr _strlwr
#define Q_strupr _strupr
#define Q_sprintf sprintf
@ -144,18 +146,17 @@ inline char *_strlwr(char *start)
#define Q_fmod fmod
#endif // #if defined(ASMLIB_H) && defined(HAVE_OPT_STRTOOLS)
// a safe variant of strcpy that truncates the result to fit in the destination buffer
template <size_t size>
char *Q_strlcpy(char (&dest)[size], const char *src) {
// size - sizeof(buffer)
inline char *Q_strlcpy(char *dest, const char *src, size_t size) {
Q_strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
return dest;
}
inline char *Q_strnlcpy(char *dest, const char *src, size_t n) {
Q_strncpy(dest, src, n - 1);
dest[n - 1] = '\0';
return dest;
// a safe variant of strcpy that truncates the result to fit in the destination buffer
template <size_t size>
char *Q_strlcpy(char (&dest)[size], const char *src) {
return Q_strlcpy(dest, src, size);
}
// safely concatenate two strings.

View File

@ -3,8 +3,6 @@
#include "cppunitlite/TestHarness.h"
TEST(PrefixedKeysRemove, Info, 1000) {
EngineInitializer engInitGuard;
struct testdata_t {
const char* inData;
const char* outData;
@ -12,9 +10,7 @@ TEST(PrefixedKeysRemove, Info, 1000) {
testdata_t testdata[] = {
{ "", "" },
{ "key\\value", "key\\value" },
{ "\\key\\value", "\\key\\value" },
{ "_key\\value", "" },
{ "\\_key\\value", "" },
{ "\\k\\v\\_u\\t\\y\\z", "\\k\\v\\y\\z" },
{ "\\_k\\v\\u\\t\\y\\z", "\\u\\t\\y\\z" },
@ -34,8 +30,6 @@ TEST(PrefixedKeysRemove, Info, 1000) {
}
TEST(SetValueForStarKey, Info, 1000) {
EngineInitializer engInitGuard;
struct testdata_t {
const char* initialInfo;
const char* key;
@ -45,20 +39,26 @@ TEST(SetValueForStarKey, Info, 1000) {
testdata_t testdata[] = {
// Behavior
{ "", "a", "b", "\\a\\b" },
{ "\\a\\b\\c\\d", "a", "b", "\\c\\d\\a\\b" },
{ "a\\b\\c\\d", "a", "b", "\\c\\d\\a\\b" },
{ "\\a\\b", "c", "d", "\\a\\b\\c\\d" },
{ "\\a\\b\\c\\d", "b", "f", "\\a\\b\\c\\d\\b\\f" },
{ "\\a\\b\\c\\d", "c", "", "\\a\\b" },
{ "\\a\\b\\c\\d\\e\\f", "c", "", "\\a\\b\\e\\f" },
{ "\\a\\b\\c\\d\\e\\f", "z", "", "\\a\\b\\c\\d\\e\\f" },
{ "\\a\\b\\c\\d", "a", "e", "\\c\\d\\a\\e" },
{ "\\a\\b\\c\\d", "e", "f", "\\a\\b\\c\\d\\e\\f" },
{ "a\\b\\c\\d", "e", "f", "a\\b\\c\\d\\e\\f" },
{ "\\a\\b\\c\\d", "b", "c", "\\a\\b\\c\\d\\b\\c" },
{ "\\a\\b\\c\\d\\e\\f", "c", "q", "\\a\\b\\e\\f\\c\\q" },
{ "\\a\\b\\c\\d", "team", "aBcD", "\\a\\b\\c\\d\\team\\abcd" },
{ "\\a\\b\\c", "e", "f", "\\a\\b\\c\\e\\f" },
{ "\\a\\b\\c\\", "e", "f", "\\a\\b\\c\\\\e\\f" },
{ "\\a\\b\\\\c", "e", "f", "\\a\\b\\\\c\\e\\f" },
// Invalid keys/values
{ "\\a\\b", "c", nullptr, "\\a\\b" },
{ "\\a\\b", nullptr, "c", "\\a\\b" },
{ "\\a\\b", "c", "d..", "\\a\\b" },
{ "\\a\\b", "..c", "d", "\\a\\b" },
{ "\\a\\b", "c\"", "d", "\\a\\b" },
{ "\\a\\b", "c", "d\"", "\\a\\b" },
{ "\\a\\b", "c\\", "d", "\\a\\b" },
{ "\\a\\b", "c", "d\\", "\\a\\b" },
//limits
{ //do nothing since 'team' is not important key
"\\cl_updaterate\\100\\topcolor\\60\\name\\abcdefghijklmnop\\*sid\\12332432525345\\_vgui_menus\\1\\model\\urban\\somelargeuselesskey\\12312321321323123123123213123123123123123123123123123123123123123dasdsad\\_cl_autowepswitch\\1",
@ -95,9 +95,44 @@ TEST(SetValueForStarKey, Info, 1000) {
}
}
TEST(RemoveKeyValue, Info, 1000) {
EngineInitializer engInitGuard;
#ifdef REHLDS_FIXES
TEST(SetValueForStarKeyResult, Info, 1000) {
struct testdata_t {
const char* initialInfo;
const char* key;
const char* value;
bool success;
};
testdata_t testdata[] = {
// Behavior
{ "\\a\\b", "c", "d", true },
{ "\\a\\b\\c\\d", "b", "f", true },
{ "\\a\\b\\c\\d", "b", "c", true },
// Invalid keys/values
{ "\\a\\b", "c", nullptr, false },
{ "\\a\\b", nullptr, "c", false },
{ "\\a\\b", "c", "d..", false },
{ "\\a\\b", "..c", "d", false },
{ "\\a\\b", "c\"", "d", false },
{ "\\a\\b", "c", "d\"", false },
{ "\\a\\b", "c\\", "d", false },
{ "\\a\\b", "c", "d\\", false },
};
for (int i = 0; i < ARRAYSIZE(testdata); i++) {
testdata_t* d = &testdata[i];
char localInfo[256];
strcpy(localInfo, d->initialInfo);
localInfo[255] = 0;
bool result = Info_SetValueForStarKey(localInfo, d->key, d->value, 256);
CHECK("Invalid info string", d->success == result);
}
}
#endif
TEST(RemoveKeyValue, Info, 1000) {
struct testdata_t {
const char* initialInfo;
const char* key;
@ -107,18 +142,14 @@ TEST(RemoveKeyValue, Info, 1000) {
testdata_t testdata[] = {
{ "", "a", "" },
{ "\\a\\b", "a", "" },
{ "\\a\\", "a", "" },
{ "\\a\\\\", "a", "\\" },
{ "\\a", "a", "" },
{ "a", "a", "" },
{ "a\\", "a", "" },
{ "a\\b", "a", "" },
{ "a\\b\\", "a", "\\" },
{ "\\a\\b\\c\\d\\e\\f", "d", "\\a\\b\\c\\d\\e\\f" },
{ "\\a\\b\\c\\d\\e\\f", "c", "\\a\\b\\e\\f" },
{ "a\\b\\c\\d\\e\\f", "d", "a\\b\\c\\d\\e\\f" },
{ "a\\b\\c\\d\\e\\f", "c", "a\\b\\e\\f" },
{ "a\\b\\c\\d\\e\\f", "a", "\\c\\d\\e\\f" },
#ifdef REHLDS_FIXES
{ "\\abc\\def\\x\\y\\ab\\cd", "ab", "\\abc\\def\\x\\y" },
#else
{ "\\abc\\def\\x\\y\\ab\\cd", "ab", "\\x\\y" },
#endif
{ "\\ab\\cd", "abc", "\\ab\\cd" }
};
for (int i = 0; i < ARRAYSIZE(testdata); i++) {
@ -131,8 +162,6 @@ TEST(RemoveKeyValue, Info, 1000) {
}
TEST(GetKeyValue, Info, 1000) {
EngineInitializer engInitGuard;
struct testdata_t {
const char* info;
const char* key;
@ -145,16 +174,8 @@ TEST(GetKeyValue, Info, 1000) {
{ "\\a\\", "a", "" },
{ "\\a\\\\", "a", "" },
{ "\\a", "a", "" },
{ "a", "a", "" },
{ "a\\", "a", "" },
{ "a\\b", "a", "b" },
{ "a\\b\\", "a", "b" },
{ "\\a\\b\\c\\d\\e\\f", "d", "" },
{ "\\a\\b\\c\\d\\e\\f", "c", "d" },
{ "a\\b\\c\\d\\e\\f", "d", "" },
{ "a\\b\\c\\d\\e\\f", "c", "d" },
{ "a\\b\\c\\d\\e\\f", "e", "f" },
{ "\\a\\b\\c\\d\\e\\f", "e", "f" },
};
@ -166,3 +187,122 @@ TEST(GetKeyValue, Info, 1000) {
ZSTR_EQUAL("Invalid info value", d->result, res);
}
}
TEST(FindLargestKey, Info, 1000) {
struct testdata_t {
const char* info;
const char* result;
};
testdata_t testdata[] = {
{ "", "" },
{ "\\name\\a\\model\\b", "" },
{ "\\name\\a\\model\\b\\c\\d", "c" },
{ "\\name\\a\\1234567890abcdef\\b\\model\\c\\1234567890abcdefghi\\d\\rate\\1000", "1234567890abcdefghi" },
{ "\\name\\a\\1234567890abcdefghi\\b\\model\\c\\1234567890abcdef\\d\\rate\\1000", "1234567890abcdefghi" }
};
for (int i = 0; i < ARRAYSIZE(testdata); i++) {
testdata_t* d = &testdata[i];
const char* res = Info_FindLargestKey(d->info, MAX_INFO_STRING);
ZSTR_EQUAL("Invalid info value", d->result, res);
}
}
TEST(InfoIsValid, Info, 1000) {
struct testdata_t {
const char* info;
qboolean result;
};
testdata_t testdata[] = {
{ "", false }, // by original design
#ifdef REHLDS_FIXES
{ "a\\b", false },
#endif
{ "\\a\\b", true },
{ "\\a\\b\\c", false },
{ "\\a\\b\\c\\", false },
{ "\\a\\b\\c\\\\", false },
#ifdef REHLDS_FIXES
{ "\\a\\b\\\\d", false },
{ "\\a\\b\\\\d\\", false },
{ "\\a\\b..c", false },
{ "\\a\\b\"c", false },
{ "\\a..b\\c", false },
{ "\\a\"b\\c", false },
#endif
{ "\\a\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", false },
{ "\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\c", false },
#ifdef REHLDS_FIXES
{ "\\a\\b\\c\\d\\a\\e", false },
#endif
{ "\\aaab\\b\\c\\d\\aaa\\e", true },
{ "\\aaa\\b\\c\\d\\aaab\\e", true }
};
for (int i = 0; i < ARRAYSIZE(testdata); i++) {
testdata_t* d = &testdata[i];
char error[256];
snprintf(error, sizeof error, "Invalid result for string '%s'", d->info);
qboolean res = Info_IsValid(d->info);
CHECK(error, d->result == res);
}
}
#ifdef REHLDS_FIXES
TEST(InfoCollectFields, Info, 1000)
{
struct testdata_t {
const char* info;
const char* cvar;
const char* result;
};
testdata_t testdata[] = {
{ "\\cl_updaterate\\100\\topcolor\\60\\name\\abcdefghijklmnop\\*sid\\12332432525345\\_vgui_menus\\1\\model\\urban\\anykey\\1", "", "\\cl_updaterate\\100\\topcolor\\60\\name\\abcdefghijklmnop\\*sid\\12332432525345\\model\\urban\\anykey\\1" },
{ "\\cl_updaterate\\100\\topcolor\\60\\name\\abcdefghijklmnop\\*sid\\12332432525345\\_vgui_menus\\1\\model\\urban", "rate", "" },
{ "\\cl_updaterate\\100\\topcolor\\60\\name\\abcdefghijklmnop\\*sid\\12332432525345\\_vgui_menus\\1\\model\\urban", "topcolor\\*sid\\_vgui_menus", "\\topcolor\\60\\*sid\\12332432525345" },
{ "\\*hltv\\1dsgs", "*hltv", "\\*hltv\\1" },
{ "\\name\\player", "bottomcolor\\name", "\\name\\player" },
};
for (int i = 0; i < ARRAYSIZE(testdata); i++) {
testdata_t* d = &testdata[i];
char destinfo[MAX_INFO_STRING];
sv_rehlds_userinfo_transmitted_fields.string = (char *)d->cvar;
Info_SetFieldsToTransmit();
Info_CollectFields(destinfo, d->info, MAX_INFO_STRING);
ZSTR_EQUAL("Invalid info string", d->result, destinfo);
}
}
#endif
TEST(Info_IsKeyImportant, Info, 1000)
{
struct testdata_t {
const char* key;
bool result;
};
testdata_t testdata[] = {
{ "bottomcolor", true },
{ "bot", false },
{ "*any", true },
{ "_any", false },
{ "model2", false }
};
for (int i = 0; i < ARRAYSIZE(testdata); i++) {
testdata_t* d = &testdata[i];
bool result = Info_IsKeyImportant(d->key);
CHECK("wrong result", d->result == result);
}
}