Core/Chat: move chat link validation code into separate classes and improve some validation logic

Core/Misc: fix non-pch build
This commit is contained in:
Azazel
2011-04-27 16:22:42 +06:00
parent d42c048818
commit 791130f6fa
7 changed files with 903 additions and 616 deletions

View File

@@ -34,37 +34,7 @@
#include "UpdateMask.h"
#include "SpellMgr.h"
#include "ScriptMgr.h"
#ifdef TRINITY_DEBUG
#define LOG(...) sLog->outDebug(LOG_FILTER_CHATSYS, __VA_ARGS__);
#else
#define LOG(...)
#endif
// 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
// - client, item icon shift click, not used in server currently
// |color|Harea:area_id|h[name]|h|r
// |color|Hcreature:creature_guid|h[name]|h|r
// |color|Hcreature_entry:creature_id|h[name]|h|r
// |color|Henchant:recipe_spell_id|h[prof_name: recipe_name]|h|r - client, at shift click in recipes list dialog
// |color|Hgameevent:id|h[name]|h|r
// |color|Hgameobject:go_guid|h[name]|h|r
// |color|Hgameobject_entry:go_id|h[name]|h|r
// |color|Hglyph:glyph_slot_id:glyph_prop_id|h[%s]|h|r - client, at shift click in glyphs dialog, GlyphSlot.dbc, GlyphProperties.dbc
// |color|Hitem:item_id:perm_ench_id:gem1:gem2:gem3:0:0:0:0:reporter_level|h[name]|h|r
// - client, item icon shift click
// |color|Hitemset:itemset_id|h[name]|h|r
// |color|Hplayer:name|h[name]|h|r - client, in some messages, at click copy only name instead link
// |color|Hquest:quest_id:quest_level|h[name]|h|r - client, quest list name shift-click
// |color|Hskill:skill_id|h[name]|h|r
// |color|Hspell:spell_id|h[name]|h|r - client, spellbook spell icon shift-click
// |color|Htalent:talent_id,rank|h[name]|h|r - client, talent icon shift-click
// |color|Htaxinode:id|h[name]|h|r
// |color|Htele:id|h[name]|h|r
// |color|Htitle:id|h[name]|h|r
// |color|Htrade:spell_id:cur_value:max_value:unk3int:unk3str|h[name]|h|r - client, spellbook profession icon shift-click
#include "ChatLink.h"
bool ChatHandler::load_command_table = true;
@@ -850,47 +820,10 @@ int ChatHandler::ParseCommands(const char* text)
return 1;
}
inline uint32 ChatHandler::_ReadUInt32(std::istringstream& reader) const
{
uint32 res = 0;
char c = reader.peek();
while (c >='0' && c <= '9')
{
reader.ignore(1);
res *= 10;
res += c - '0';
c = reader.peek();
}
return res;
}
inline int32 ChatHandler::_ReadInt32(std::istringstream& reader) const
{
int32 res = 0;
bool isNegative = false;
char c = reader.peek();
while ((c >= '0' && c <= '9') || c == '-')
{
if (c >='0' && c <= '9')
{
res *= 10;
res += c - '0';
}
else if (c == '-')
isNegative = true;
reader.ignore(1);
c = reader.peek();
}
if (isNegative)
res = -res;
return res;
}
bool ChatHandler::isValidChatMessage(const char* message)
{
/*
valid examples:
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
@@ -906,12 +839,11 @@ valid examples:
if (strlen(message) > 255)
return false;
const char validSequence[6] = "cHhhr";
const char* validSequenceIterator = validSequence;
// more simple checks
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY) < 3)
{
const char validSequence[6] = "cHhhr";
const char* validSequenceIterator = validSequence;
const std::string validCommands = "cHhr|";
while (*message)
@@ -933,7 +865,7 @@ valid examples:
{
if (commandChar == *validSequenceIterator)
{
if (validSequenceIterator == validSequence+4)
if (validSequenceIterator == validSequence + 4)
validSequenceIterator = validSequence;
else
++validSequenceIterator;
@@ -945,539 +877,7 @@ valid examples:
return true;
}
std::istringstream reader(message);
char buffer[256];
uint32 color = 0;
ItemPrototype const* linkedItem = NULL;
Quest const* linkedQuest = NULL;
SpellEntry const *linkedSpell = NULL;
AchievementEntry const* linkedAchievement = NULL;
ItemRandomPropertiesEntry const* itemProperty = NULL;
ItemRandomSuffixEntry const* itemSuffix = NULL;
while (!reader.eof())
{
if (validSequence == validSequenceIterator)
{
linkedItem = NULL;
linkedQuest = NULL;
linkedSpell = NULL;
linkedAchievement = NULL;
itemProperty = NULL;
itemSuffix = NULL;
reader.ignore(255, '|');
}
else if (reader.get() != '|')
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence aborted unexpectedly", reader.str().c_str());
return false;
}
// pipe has always to be followed by at least one char
if (reader.peek() == '\0')
{
LOG("ChatHandler::isValidChatMessage('%s'): pipe followed by '\\0'", reader.str().c_str());
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
{
LOG("ChatHandler::isValidChatMessage('%s'): invalid sequence, expected '%c' but got '%c'", reader.str().c_str(), *validSequenceIterator, commandChar);
return false;
}
}
else if (validSequence != validSequenceIterator)
{
// no escaped pipes in sequences
LOG("ChatHandler::isValidChatMessage('%s'): got escaped pipe in sequence", reader.str().c_str());
return false;
}
switch (commandChar)
{
case 'c':
color = 0;
// validate color, expect 8 hex chars
for (uint8 i = 0; i < 8; ++i)
{
char c;
reader >> c;
if (!c)
{
LOG("ChatHandler::isValidChatMessage('%s'): got \\0 while reading color in |c command", reader.str().c_str());
return false;
}
color <<= 4;
// check for hex char
if (c >= '0' && c <= '9')
color |= c - '0';
else if (c >= 'a' && c <= 'f')
color |= 10 + c - 'a';
else
{
LOG("ChatHandler::isValidChatMessage('%s'): got non hex char '%c' while reading color", reader.str().c_str(), c);
return false;
}
}
break;
case 'H':
// read chars up to colon = link type
reader.getline(buffer, 256, ':');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
if (strcmp(buffer, "item") == 0)
{
// read item entry
reader.getline(buffer, 256, ':');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
uint32 itemEntry = atoi(buffer);
linkedItem = ObjectMgr::GetItemPrototype(itemEntry);
if (!linkedItem)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid itemEntry %u in |item command", reader.str().c_str(), itemEntry);
return false;
}
if (color != ItemQualityColors[linkedItem->Quality])
{
LOG("ChatHandler::isValidChatMessage('%s'): linked item has color %u, but user claims %u", reader.str().c_str(), ItemQualityColors[linkedItem->Quality], color);
return false;
}
// The itementry is followed by several integers which describe an instance of this item
const uint8 randomPropertyPosition = 6;
char c = '\0';
int32 id = 0;
for (uint8 i = 0; i < randomPropertyPosition; ++i)
{
id = _ReadInt32(reader);
c = reader.get();
if (c != ':')
{
LOG("ChatHandler::isValidChatMessage('%s'): invalid character '%c' found while reading item properties", reader.str().c_str(), c);
return false;
}
}
// Validate random property
if (id > 0)
{
itemProperty = sItemRandomPropertiesStore.LookupEntry(id);
if (!itemProperty)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid item property id %u in |item command", reader.str().c_str(), id);
return false;
}
}
else if (id < 0)
{
itemSuffix = sItemRandomSuffixStore.LookupEntry(-id);
if (!itemSuffix)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid item suffix id %u in |item command", reader.str().c_str(), -id);
return false;
}
}
// ignore other integers
while ((c >= '0' && c <= '9') || c == ':')
{
reader.ignore(1);
c = reader.peek();
}
}
else if (strcmp(buffer, "quest") == 0)
{
// Quest Id
uint32 questId = _ReadUInt32(reader);
linkedQuest = sObjectMgr->GetQuestTemplate(questId);
if (!linkedQuest)
{
LOG("ChatHandler::isValidChatMessage('%s'): quest template %u not found", reader.str().c_str(), questId);
return false;
}
// Delimiter
char c = reader.peek();
if (c != ':')
{
LOG("ChatHandler::isValidChatMessage('%s'): invalid quest link structure (':' expected, '%c' found)", reader.str().c_str(), c);
return false;
}
reader.ignore(1);
// Quest level
int32 questLevel = _ReadInt32(reader);
if (questLevel >= STRONG_MAX_LEVEL)
{
LOG("ChatHandler::isValidChatMessage('%s'): quest level %d is too big", reader.str().c_str(), questLevel);
return false;
}
}
else if (strcmp(buffer, "trade") == 0)
{
if (color != CHAT_LINK_COLOR_TRADE)
return false;
// Spell Id
reader.getline(buffer, 256, ':');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
uint32 spellId = atoi(buffer);
linkedSpell = sSpellStore.LookupEntry(spellId);
if (!linkedSpell)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |trade command", reader.str().c_str(), spellId);
return false;
}
// base64 encoded stuff
char c = reader.peek();
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, ':');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
uint32 talentId = atoi(buffer);
TalentEntry const *talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid talent id %u in |talent command", reader.str().c_str(), talentId);
return false;
}
linkedSpell = sSpellStore.LookupEntry(talentInfo->RankID[0]);
if (!linkedSpell)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |trade command", reader.str().c_str(), talentInfo->RankID[0]);
return false;
}
// skillpoints? whatever, drop it
char c = reader.peek();
while (c != '|' && c != '\0')
{
reader.ignore(1);
c = reader.peek();
}
}
else if (strcmp(buffer, "spell") == 0)
{
if (color != CHAT_LINK_COLOR_SPELL)
return false;
// Spell Id
uint32 spellId = _ReadUInt32(reader);
linkedSpell = sSpellStore.LookupEntry(spellId);
if (!linkedSpell)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |spell command", reader.str().c_str(), spellId);
return false;
}
}
else if (strcmp(buffer, "enchant") == 0)
{
if (color != CHAT_LINK_COLOR_ENCHANT)
return false;
// Spell Id
uint32 spellId = _ReadUInt32(reader);
linkedSpell = sSpellStore.LookupEntry(spellId);
if (!linkedSpell)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |enchant command", reader.str().c_str(), spellId);
return false;
}
}
else if (strcmp(buffer, "achievement") == 0)
{
if (color != CHAT_LINK_COLOR_ACHIEVEMENT)
return false;
// Achievemnt Id
reader.getline(buffer, 256, ':');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
uint32 achievementId = atoi(buffer);
linkedAchievement = sAchievementStore.LookupEntry(achievementId);
if (!linkedAchievement)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid achivement id %u in |achievement command", reader.str().c_str(), achievementId);
return false;
}
// skip progress
char c = reader.peek();
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, ':');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
uint32 glyphId = _ReadUInt32(reader);
GlyphPropertiesEntry const* glyph = sGlyphPropertiesStore.LookupEntry(glyphId);
if (!glyph)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid glyph id %u in |glyph command", reader.str().c_str(), glyphId);
return false;
}
linkedSpell = sSpellStore.LookupEntry(glyph->SpellId);
if (!linkedSpell)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |glyph command", reader.str().c_str(), glyph->SpellId);
return false;
}
}
else
{
LOG("ChatHandler::isValidChatMessage('%s'): user sent unsupported link type '%s'", reader.str().c_str(), buffer);
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() != '[')
{
LOG("ChatHandler::isValidChatMessage('%s'): link caption doesn't start with '['", reader.str().c_str());
return false;
}
reader.getline(buffer, 256, ']');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
// verify the link name
if (linkedSpell)
{
// spells with that flag have a prefix of "$PROFESSION: "
if (linkedSpell->Attributes & SPELL_ATTR0_TRADESPELL)
{
// lookup skillid
SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(linkedSpell->Id);
if (bounds.first == bounds.second)
{
LOG("ChatHandler::isValidChatMessage('%s'): skill line not found for spell %u", reader.str().c_str(), linkedSpell->Id);
return false;
}
SkillLineAbilityEntry const *skillInfo = bounds.first->second;
if (!skillInfo)
{
LOG("ChatHandler::isValidChatMessage('%s'): skill line ability not found for spell %u", reader.str().c_str(), linkedSpell->Id);
return false;
}
SkillLineEntry const *skillLine = sSkillLineStore.LookupEntry(skillInfo->skillId);
if (!skillLine)
{
LOG("ChatHandler::isValidChatMessage('%s'): skill line not found for skill %u", reader.str().c_str(), skillInfo->skillId);
return false;
}
for (uint8 i = 0; i < TOTAL_LOCALES; ++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 < TOTAL_LOCALES; ++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 = sObjectMgr->GetQuestLocale(linkedQuest->GetQuestId());
if (!ql)
{
LOG("ChatHandler::isValidChatMessage('%s'): default questname didn't match and no locales present for this quest (id: %u)", reader.str().c_str(), linkedQuest->GetQuestId());
return false;
}
bool foundName = false;
for (uint8 i=0; i<ql->Title.size(); i++)
{
if (ql->Title[i] == buffer)
{
foundName = true;
break;
}
}
if (!foundName)
{
LOG("ChatHandler::isValidChatMessage('%s'): linked quest (id: %u) name wasn't found in any localization", reader.str().c_str(), linkedQuest->GetQuestId());
return false;
}
}
}
else if (linkedItem)
{
char* const* suffix = itemSuffix ? itemSuffix->nameSuffix :
(itemProperty ? itemProperty->nameSuffix : NULL);
std::string expectedName = std::string(linkedItem->Name1);
if (suffix)
{
expectedName += " ";
expectedName += suffix[LOCALE_enUS];
}
if (expectedName != buffer)
{
ItemLocale const *il = sObjectMgr->GetItemLocale(linkedItem->ItemId);
bool foundName = false;
for (uint8 dbIndex = LOCALE_koKR; dbIndex < TOTAL_LOCALES; ++dbIndex)
{
if (il == NULL || dbIndex >= il->Name.size())
// using strange database/client combinations can lead to this case
expectedName = linkedItem->Name1;
else
expectedName = il->Name[dbIndex];
if (suffix)
{
expectedName += " ";
expectedName += suffix[dbIndex];
}
if (expectedName == buffer)
{
foundName = true;
break;
}
}
if (!foundName)
{
LOG("ChatHandler::isValidChatMessage('%s'): linked item (id: %u) name wasn't found in any localization", reader.str().c_str(), linkedItem->ItemId);
return false;
}
}
}
else if (linkedAchievement)
{
bool foundName = false;
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
{
if (*linkedAchievement->name[i] && strcmp(linkedAchievement->name[i], buffer) == 0)
{
foundName = true;
break;
}
}
if (!foundName)
{
LOG("ChatHandler::isValidChatMessage('%s'): linked achievement (id: %u) name wasn't found in any localization", reader.str().c_str(), linkedAchievement->ID);
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:
LOG("ChatHandler::isValidChatMessage('%s'): got invalid command |%c", reader.str().c_str(), commandChar);
return false;
}
}
// check if every opened sequence was also closed properly
if (validSequence != validSequenceIterator)
{
LOG("ChatHandler::isValidChatMessage('%s'): EOF in active sequence", reader.str().c_str());
return false;
}
return true;
return LinkExtractor(message).IsValidMessage();
}
bool ChatHandler::ShowHelpForSubCommands(ChatCommand *table, char const* cmd, char const* subcmd)