diff --git a/datasrc/network.py b/datasrc/network.py index cd5f5aee9..b2c42b321 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -231,7 +231,7 @@ Messages = [ NetMessage("Sv_Chat", [ NetIntRange("m_Team", 'TEAM_SPECTATORS', 'TEAM_BLUE'), NetIntRange("m_ClientID", -1, 'MAX_CLIENTS-1'), - NetString("m_pMessage"), + NetStringStrict("m_pMessage"), ]), NetMessage("Sv_KillMsg", [ @@ -294,7 +294,7 @@ Messages = [ ### Client messages NetMessage("Cl_Say", [ NetBool("m_Team"), - NetString("m_pMessage"), + NetStringStrict("m_pMessage"), ]), NetMessage("Cl_SetTeam", [ diff --git a/src/base/system.c b/src/base/system.c index 410cb699a..49150b4e3 100644 --- a/src/base/system.c +++ b/src/base/system.c @@ -1829,6 +1829,27 @@ int str_toint(const char *str) { return atoi(str); } float str_tofloat(const char *str) { return atof(str); } +char *str_utf8_skip_whitespaces(char *str) +{ + char *str_old; + int code; + + while(*str) + { + str_old = str; + code = str_utf8_decode(&str); + + // check if unicode is not empty + if(code > 0x20 && code != 0xA0 && code != 0x034F && (code < 0x2000 || code > 0x200F) && (code < 0x2028 || code > 0x202F) && + (code < 0x205F || code > 0x2064) && (code < 0x206A || code > 0x206F) && (code < 0xFE00 || code > 0xFE0F) && + code != 0xFEFF && (code < 0xFFF9 || code > 0xFFFC)) + { + return str_old; + } + } + + return str; +} static int str_utf8_isstart(char c) { diff --git a/src/base/system.h b/src/base/system.h index 7ba0c0a09..871473406 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -1213,6 +1213,7 @@ unsigned str_quickhash(const char *str); */ void gui_messagebox(const char *title, const char *message); +char *str_utf8_skip_whitespaces(char *str); /* Function: str_utf8_rewind diff --git a/src/engine/shared/packer.cpp b/src/engine/shared/packer.cpp index cc218825c..788e564d0 100644 --- a/src/engine/shared/packer.cpp +++ b/src/engine/shared/packer.cpp @@ -136,7 +136,7 @@ const char *CUnpacker::GetString(int SanitizeType) str_sanitize(pPtr); else if(SanitizeType&SANITIZE_CC) str_sanitize_cc(pPtr); - return SanitizeType&SKIP_START_WHITESPACES ? str_skip_whitespaces(pPtr) : pPtr; + return SanitizeType&SKIP_START_WHITESPACES ? str_utf8_skip_whitespaces(pPtr) : pPtr; } const unsigned char *CUnpacker::GetRaw(int Size) diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 5b2831df4..5da7e6312 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -282,6 +282,34 @@ void CChat::AddLine(int ClientID, int Team, const char *pLine) (m_pClient->m_Snap.m_LocalClientID != ClientID && g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[ClientID].m_Friend)))) return; + // trim right and set maximum length to 128 utf8-characters + int Length = 0; + const char *pStr = pLine; + const char *pEnd = 0; + while(*pStr) + { + const char *pStrOld = pStr; + int Code = str_utf8_decode(&pStr); + + // check if unicode is not empty + if(Code > 0x20 && Code != 0xA0 && Code != 0x034F && (Code < 0x2000 || Code > 0x200F) && (Code < 0x2028 || Code > 0x202F) && + (Code < 0x205F || Code > 0x2064) && (Code < 0x206A || Code > 0x206F) && (Code < 0xFE00 || Code > 0xFE0F) && + Code != 0xFEFF && (Code < 0xFFF9 || Code > 0xFFFC)) + { + pEnd = 0; + } + else if(pEnd == 0) + pEnd = pStrOld; + + if(++Length >= 127) + { + *(const_cast(pStr)) = 0; + break; + } + } + if(pEnd != 0) + *(const_cast(pEnd)) = 0; + bool Highlighted = false; char *p = const_cast(pLine); while(*p) diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index d625d4c0d..111cad6a8 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -243,7 +243,8 @@ int CMenus::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrS for(int i = 0; i < m_NumInputEvents; i++) { Len = str_length(pStr); - ReturnValue |= CLineInput::Manipulate(m_aInputEvents[i], pStr, StrSize, &Len, &s_AtIndex); + int NumChars = Len; + ReturnValue |= CLineInput::Manipulate(m_aInputEvents[i], pStr, StrSize, StrSize, &Len, &s_AtIndex, &NumChars); } } diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp index 2de85d667..11fae570b 100644 --- a/src/game/client/lineinput.cpp +++ b/src/game/client/lineinput.cpp @@ -13,6 +13,7 @@ void CLineInput::Clear() mem_zero(m_Str, sizeof(m_Str)); m_Len = 0; m_CursorPos = 0; + m_NumChars = 0; } void CLineInput::Set(const char *pString) @@ -20,10 +21,18 @@ void CLineInput::Set(const char *pString) str_copy(m_Str, pString, sizeof(m_Str)); m_Len = str_length(m_Str); m_CursorPos = m_Len; + m_NumChars = 0; + int Offset = 0; + while(pString[Offset]) + { + Offset = str_utf8_forward(pString, Offset); + ++m_NumChars; + } } -bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int *pStrLenPtr, int *pCursorPosPtr) +bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int StrMaxChars, int *pStrLenPtr, int *pCursorPosPtr, int *pNumCharsPtr) { + int NumChars = *pNumCharsPtr; int CursorPos = *pCursorPosPtr; int Len = *pStrLenPtr; bool Changes = false; @@ -40,13 +49,15 @@ bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int *p char Tmp[8]; int CharSize = str_utf8_encode(Tmp, Code); - if (Len < StrMaxSize - CharSize && CursorPos < StrMaxSize - CharSize) + if (Len < StrMaxSize - CharSize && CursorPos < StrMaxSize - CharSize && NumChars < StrMaxChars) { mem_move(pStr + CursorPos + CharSize, pStr + CursorPos, Len-CursorPos+1); // +1 == null term for(int i = 0; i < CharSize; i++) pStr[CursorPos+i] = Tmp[i]; CursorPos += CharSize; Len += CharSize; + if(CharSize > 0) + ++NumChars; Changes = true; } } @@ -60,6 +71,8 @@ bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int *p mem_move(pStr+NewCursorPos, pStr+CursorPos, Len - NewCursorPos - CharSize + 1); // +1 == null term CursorPos = NewCursorPos; Len -= CharSize; + if(CharSize > 0) + --NumChars; Changes = true; } else if (k == KEY_DELETE && CursorPos < Len) @@ -68,6 +81,8 @@ bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int *p int CharSize = p-CursorPos; mem_move(pStr + CursorPos, pStr + CursorPos + CharSize, Len - CursorPos - CharSize + 1); // +1 == null term Len -= CharSize; + if(CharSize > 0) + --NumChars; Changes = true; } else if (k == KEY_LEFT && CursorPos > 0) @@ -80,6 +95,7 @@ bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int *p CursorPos = Len; } + *pNumCharsPtr = NumChars; *pCursorPosPtr = CursorPos; *pStrLenPtr = Len; @@ -88,5 +104,5 @@ bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int *p void CLineInput::ProcessInput(IInput::CEvent e) { - Manipulate(e, m_Str, sizeof(m_Str), &m_Len, &m_CursorPos); + Manipulate(e, m_Str, MAX_SIZE, MAX_CHARS, &m_Len, &m_CursorPos, &m_NumChars); } diff --git a/src/game/client/lineinput.h b/src/game/client/lineinput.h index 5dfc3e489..f6cb00162 100644 --- a/src/game/client/lineinput.h +++ b/src/game/client/lineinput.h @@ -8,11 +8,17 @@ // line input helter class CLineInput { - char m_Str[256]; + enum + { + MAX_SIZE=512, + MAX_CHARS=MAX_SIZE/4, + }; + char m_Str[MAX_SIZE]; int m_Len; int m_CursorPos; + int m_NumChars; public: - static bool Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int *pStrLenPtr, int *pCursorPosPtr); + static bool Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int StrMaxChars, int *pStrLenPtr, int *pCursorPosPtr, int *pNumCharsPtr); class CCallback { diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 28a4eda96..e7b917f4d 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -277,7 +277,8 @@ int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned Str for(int i = 0; i < Input()->NumEvents(); i++) { Len = str_length(pStr); - ReturnValue |= CLineInput::Manipulate(Input()->GetEvent(i), pStr, StrSize, &Len, &s_AtIndex); + int NumChars = Len; + ReturnValue |= CLineInput::Manipulate(Input()->GetEvent(i), pStr, StrSize, StrSize, &Len, &s_AtIndex, &NumChars); } } diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index a2500dda6..f1c533504 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -612,26 +612,45 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) { if(MsgID == NETMSGTYPE_CL_SAY) { - CNetMsg_Cl_Say *pMsg = (CNetMsg_Cl_Say *)pRawMsg; - int Team = pMsg->m_Team; - if(Team) - Team = pPlayer->GetTeam(); - else - Team = CGameContext::CHAT_ALL; - if(g_Config.m_SvSpamprotection && pPlayer->m_LastChat && pPlayer->m_LastChat+Server()->TickSpeed() > Server()->Tick()) return; - pPlayer->m_LastChat = Server()->Tick(); + CNetMsg_Cl_Say *pMsg = (CNetMsg_Cl_Say *)pRawMsg; + int Team = pMsg->m_Team ? pPlayer->GetTeam() : CGameContext::CHAT_ALL; + + // trim right and set maximum length to 128 utf8-characters + int Length = 0; + const char *p = pMsg->m_pMessage; + const char *pEnd = 0; + while(*p) + { + const char *pStrOld = p; + int Code = str_utf8_decode(&p); - // check for invalid chars - unsigned char *pMessage = (unsigned char *)pMsg->m_pMessage; - while (*pMessage) - { - if(*pMessage < 32) - *pMessage = ' '; - pMessage++; - } + // check if unicode is not empty + if(Code > 0x20 && Code != 0xA0 && Code != 0x034F && (Code < 0x2000 || Code > 0x200F) && (Code < 0x2028 || Code > 0x202F) && + (Code < 0x205F || Code > 0x2064) && (Code < 0x206A || Code > 0x206F) && (Code < 0xFE00 || Code > 0xFE0F) && + Code != 0xFEFF && (Code < 0xFFF9 || Code > 0xFFFC)) + { + pEnd = 0; + } + else if(pEnd == 0) + pEnd = pStrOld; + + if(++Length >= 127) + { + *(const_cast(p)) = 0; + break; + } + } + if(pEnd != 0) + *(const_cast(pEnd)) = 0; + + // drop empty and autocreated spam messages (more than 16 characters per second) + if(Length == 0 || (g_Config.m_SvSpamprotection && pPlayer->m_LastChat && pPlayer->m_LastChat+Server()->TickSpeed()*((15+Length)/16) > Server()->Tick())) + return; + + pPlayer->m_LastChat = Server()->Tick(); SendChat(ClientID, Team, pMsg->m_pMessage); }