[8424] Added support for strict chatmessage validation

This commit is contained in:
arrai 2009-08-27 01:00:04 +02:00
parent 3c22e14e53
commit 43a4d1505e
13 changed files with 631 additions and 39 deletions

View file

@ -31,6 +31,7 @@
#include "GridNotifiersImpl.h"
#include "CellImpl.h"
#include "AccountMgr.h"
#include "SpellMgr.h"
// Supported shift-links (client generated and server side)
// |color|Hachievement:achievement_id:player_guid:0:0:0:0:0:0:0:0|h[name]|h|r
@ -980,6 +981,542 @@ int ChatHandler::ParseCommands(const char* text)
return 1;
}
bool ChatHandler::isValidChatMessage(const char* message)
{
/*
valid examples:
|cffa335ee|Hitem:812:0:0:0:0:0:0:0:70|h[Glowing Brightwood Staff]|h|r
|cff808080|Hquest:2278:47|h[The Platinum Discs]|h|r
|cffffd000|Htrade:4037:1:150:1:6AAAAAAAAAAAAAAAAAAAAAAOAADAAAAAAAAAAAAAAAAIAAAAAAAAA|h[Engineering]|h|r
|cff4e96f7|Htalent:2232:-1|h[Taste for Blood]|h|r
|cff71d5ff|Hspell:21563|h[Command]|h|r
|cffffd000|Henchant:3919|h[Engineering: Rough Dynamite]|h|r
|cffffff00|Hachievement:546:0000000000000001:0:0:0:-1:0:0:0:0|h[Safe Deposit]|h|r
|cff66bbff|Hglyph:21:762|h[Glyph of Bladestorm]|h|r
| will be escaped to ||
*/
if(strlen(message) > 255)
return false;
const char validSequence[6] = "cHhhr";
const char* validSequenceIterator = validSequence;
// more simple checks
if (sWorld.getConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY) < 3)
{
const std::string validCommands = "cHhr|";
while(*message)
{
// find next pipe command
message = strchr(message, '|');
if(!message)
return true;
++message;
char commandChar = *message;
if(validCommands.find(commandChar) == std::string::npos)
return false;
++message;
// validate sequence
if(sWorld.getConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY) == 2)
{
if(commandChar == *validSequenceIterator)
{
if (validSequenceIterator == validSequence+4)
validSequenceIterator = validSequence;
else
++validSequenceIterator;
}
else
return false;
}
}
return true;
}
std::istringstream reader(message);
char buffer[256];
uint32 color;
ItemPrototype const* linkedItem;
Quest const* linkedQuest;
SpellEntry const *linkedSpell;
AchievementEntry const* linkedAchievement;
while(!reader.eof())
{
if (validSequence == validSequenceIterator)
{
linkedItem = NULL;
linkedQuest = NULL;
linkedSpell = NULL;
linkedAchievement = NULL;
reader.ignore(255, '|');
}
else if(reader.get() != '|')
{
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage sequence aborted unexpectedly");
#endif
return false;
}
// pipe has always to be followed by at least one char
if ( reader.peek() == '\0')
{
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage pipe followed by \\0");
#endif
return false;
}
// no further pipe commands
if (reader.eof())
break;
char commandChar;
reader >> commandChar;
// | in normal messages is escaped by ||
if (commandChar != '|')
{
if(commandChar == *validSequenceIterator)
{
if (validSequenceIterator == validSequence+4)
validSequenceIterator = validSequence;
else
++validSequenceIterator;
}
else
{
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage invalid sequence, expected %c but got %c", *validSequenceIterator, commandChar);
#endif
return false;
}
}
else if(validSequence != validSequenceIterator)
{
// no escaped pipes in sequences
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage got escaped pipe in sequence");
#endif
return false;
}
switch (commandChar)
{
case 'c':
color = 0;
// validate color, expect 8 hex chars
for(int i=0; i<8; i++)
{
char c;
reader >> c;
if(!c)
{
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage got \\0 while reading color in |c command");
#endif
return false;
}
color <<= 4;
// check for hex char
if(c >= '0' && c <='9')
{
color |= c-'0';
continue;
}
if(c >= 'a' && c <='f')
{
color |= 10+c-'a';
continue;
}
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage got non hex char '%c' while reading color", c);
#endif
return false;
}
break;
case 'H':
// read chars up to colon = link type
reader.getline(buffer, 256, ':');
if (strcmp(buffer, "item") == 0)
{
// read item entry
reader.getline(buffer, 256, ':');
linkedItem= objmgr.GetItemPrototype(atoi(buffer));
if(!linkedItem)
{
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage got invalid itemID %u in |item command", atoi(buffer));
#endif
return false;
}
if (color != ItemQualityColors[linkedItem->Quality])
{
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage linked item has color %u, but user claims %u", ItemQualityColors[linkedItem->Quality],
color);
#endif
return false;
}
char c = reader.peek();
// ignore enchants etc.
while(c >='0' && c <='9' || c==':')
{
reader.ignore(1);
c = reader.peek();
}
}
else if(strcmp(buffer, "quest") == 0)
{
// no color check for questlinks, each client will adapt it anyway
uint32 questid= 0;
// read questid
char c = reader.peek();
while(c >='0' && c<='9')
{
reader.ignore(1);
questid *= 10;
questid += c-'0';
c = reader.peek();
}
linkedQuest = objmgr.GetQuestTemplate(questid);
if(!linkedQuest)
{
#ifdef MANOGS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage Questtemplate %u not found", questid);
#endif
return false;
}
c = reader.peek();
// level
while(c !='|' && c!='\0')
{
reader.ignore(1);
c = reader.peek();
}
}
else if(strcmp(buffer, "trade") == 0)
{
if(color != CHAT_LINK_COLOR_TRADE)
return false;
// read spell entry
reader.getline(buffer, 256, ':');
linkedSpell = sSpellStore.LookupEntry(atoi(buffer));
if(!linkedSpell)
return false;
char c = reader.peek();
// base64 encoded stuff
while(c !='|' && c!='\0')
{
reader.ignore(1);
c = reader.peek();
}
}
else if(strcmp(buffer, "talent") == 0)
{
// talent links are always supposed to be blue
if(color != CHAT_LINK_COLOR_TALENT)
return false;
// read talent entry
reader.getline(buffer, 256, ':');
TalentEntry const *talentInfo = sTalentStore.LookupEntry(atoi(buffer));
if(!talentInfo)
return false;
linkedSpell = sSpellStore.LookupEntry(talentInfo->RankID[0]);
if(!linkedSpell)
return false;
char c = reader.peek();
// skillpoints? whatever, drop it
while(c !='|' && c!='\0')
{
reader.ignore(1);
c = reader.peek();
}
}
else if(strcmp(buffer, "spell") == 0)
{
if(color != CHAT_LINK_COLOR_SPELL)
return false;
uint32 spellid = 0;
// read spell entry
char c = reader.peek();
while(c >='0' && c<='9')
{
reader.ignore(1);
spellid *= 10;
spellid += c-'0';
c = reader.peek();
}
linkedSpell = sSpellStore.LookupEntry(spellid);
if(!linkedSpell)
return false;
}
else if(strcmp(buffer, "enchant") == 0)
{
if(color != CHAT_LINK_COLOR_ENCHANT)
return false;
uint32 spellid = 0;
// read spell entry
char c = reader.peek();
while(c >='0' && c<='9')
{
reader.ignore(1);
spellid *= 10;
spellid += c-'0';
c = reader.peek();
}
linkedSpell = sSpellStore.LookupEntry(spellid);
if(!linkedSpell)
return false;
}
else if(strcmp(buffer, "achievement") == 0)
{
if(color != CHAT_LINK_COLOR_ACHIEVEMENT)
return false;
reader.getline(buffer, 256, ':');
uint32 achievementId = atoi(buffer);
linkedAchievement = sAchievementStore.LookupEntry(achievementId);
if(!linkedAchievement)
return false;
char c = reader.peek();
// skip progress
while(c !='|' && c!='\0')
{
reader.ignore(1);
c = reader.peek();
}
}
else if(strcmp(buffer, "glyph") == 0)
{
if(color != CHAT_LINK_COLOR_GLYPH)
return false;
// first id is slot, drop it
reader.getline(buffer, 256, ':');
uint32 glyphId = 0;
char c = reader.peek();
while(c>='0' && c <='9')
{
glyphId *= 10;
glyphId += c-'0';
reader.ignore(1);
c = reader.peek();
}
GlyphPropertiesEntry const* glyph = sGlyphPropertiesStore.LookupEntry(glyphId);
if(!glyph)
return false;
linkedSpell = sSpellStore.LookupEntry(glyph->SpellId);
if(!linkedSpell)
return false;
}
else
{
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage user sent unsupported link type '%s'", buffer);
#endif
return false;
}
break;
case 'h':
// if h is next element in sequence, this one must contain the linked text :)
if(*validSequenceIterator == 'h')
{
// links start with '['
if(reader.get() != '[')
{
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage link caption doesn't start with '['");
#endif
return false;
}
reader.getline(buffer, 256, ']');
// verify the link name
if (linkedSpell)
{
// spells with that flag have a prefix of "$PROFESSION: "
if(linkedSpell->Attributes & SPELL_ATTR_TRADESPELL)
{
// lookup skillid
SkillLineAbilityMap::const_iterator lower = spellmgr.GetBeginSkillLineAbilityMap(linkedSpell->Id);
SkillLineAbilityMap::const_iterator upper = spellmgr.GetEndSkillLineAbilityMap(linkedSpell->Id);
if(lower == upper)
{
return false;
}
SkillLineAbilityEntry const *skillInfo = lower->second;
if (!skillInfo)
{
return false;
}
SkillLineEntry const *skillLine = sSkillLineStore.LookupEntry(skillInfo->skillId);
if(!skillLine)
{
return false;
}
for(uint8 i=0; i<MAX_LOCALE; ++i)
{
uint32 skillLineNameLength = strlen(skillLine->name[i]);
if(skillLineNameLength > 0 && strncmp(skillLine->name[i], buffer, skillLineNameLength) == 0)
{
// found the prefix, remove it to perform spellname validation below
// -2 = strlen(": ")
uint32 spellNameLength = strlen(buffer)-skillLineNameLength-2;
memmove(buffer, buffer+skillLineNameLength+2, spellNameLength+1);
}
}
}
bool foundName = false;
for(uint8 i=0; i<MAX_LOCALE; ++i)
{
if(*linkedSpell->SpellName[i] && strcmp(linkedSpell->SpellName[i], buffer) == 0)
{
foundName = true;
break;
}
}
if(!foundName)
return false;
}
else if(linkedQuest)
{
if (linkedQuest->GetTitle() != buffer)
{
QuestLocale const *ql = objmgr.GetQuestLocale(linkedQuest->GetQuestId());
if(!ql)
{
#ifdef MANOGS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage default questname didn't match and there is no locale");
#endif
return false;
}
bool foundName = false;
for(uint8 i=0; i<ql->Title.size(); i++)
{
if(ql->Title[i] == buffer)
{
foundName = true;
break;
}
}
if(!foundName)
{
#ifdef MANOGS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage no quest locale title matched")
#endif
return false;
}
}
}
else if(linkedItem)
{
if(strcmp(linkedItem->Name1, buffer) != 0)
{
ItemLocale const *il = objmgr.GetItemLocale(linkedItem->ItemId);
if(!il)
{
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage linked item name doesn't is wrong and there is no localization");
#endif
return false;
}
bool foundName = false;
for(uint8 i=0; i<il->Name.size(); ++i)
{
if(il->Name[i] == buffer)
{
foundName = true;
break;
}
}
if(!foundName)
{
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage linked item name wasn't found in any localization");
#endif
return false;
}
}
}
else if(linkedAchievement)
{
bool foundName = false;
for(uint8 i=0; i<MAX_LOCALE; ++i)
{
if(*linkedAchievement->name[i], strcmp(linkedAchievement->name[i], buffer) == 0)
{
foundName = true;
break;
}
}
if(!foundName)
return false;
}
// that place should never be reached - if nothing linked has been set in |H
// it will return false before
else
return false;
}
break;
case 'r':
case '|':
// no further payload
break;
default:
#ifdef MANGOS_DEBUG
sLog.outBasic("ChatHandler::isValidChatMessage got invalid command |%c", commandChar);
#endif
return false;
}
}
// check if every opened sequence was also closed properly
#ifdef MANGOS_DEBUG
if(validSequence != validSequenceIterator)
sLog.outBasic("ChatHandler::isValidChatMessage EOF in active sequence");
#endif
return validSequence == validSequenceIterator;
}
bool ChatHandler::ShowHelpForSubCommands(ChatCommand *table, char const* cmd, char const* subcmd)
{
std::string list;