Core/Chat: Unify chat hyperlink parsing (PR #22417)

- Validate most link properties all the time
- If enabled, also validate link text (needs locale)
- Instead of blocking the entire message, sanitize it
- Apply filtering to DND/AFK messages. Closes #22399

(cherry picked from commit f27284594b)
This commit is contained in:
Treeston
2018-09-13 00:11:27 +02:00
committed by Shauren
parent fa7e46a4f3
commit e506c80a64
19 changed files with 1003 additions and 1500 deletions

View File

@@ -68,40 +68,6 @@ Tokenizer::Tokenizer(const std::string &src, const char sep, uint32 vectorReserv
}
}
void stripLineInvisibleChars(std::string &str)
{
static std::string const invChars = " \t\7\n";
size_t wpos = 0;
bool space = false;
for (size_t pos = 0; pos < str.size(); ++pos)
{
if (invChars.find(str[pos])!=std::string::npos)
{
if (!space)
{
str[wpos++] = ' ';
space = true;
}
}
else
{
if (wpos!=pos)
str[wpos++] = str[pos];
else
++wpos;
space = false;
}
}
if (wpos < str.size())
str.erase(wpos, str.size());
if (str.find("|TInterface")!=std::string::npos)
str.clear();
}
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
struct tm* localtime_r(time_t const* time, struct tm *result)
{

View File

@@ -55,8 +55,6 @@ private:
StorageType m_storage;
};
TC_COMMON_API void stripLineInvisibleChars(std::string &src);
TC_COMMON_API int64 MoneyStringToMoney(std::string const& moneyString);
TC_COMMON_API struct tm* localtime_r(time_t const* time, struct tm *result);

View File

@@ -19,7 +19,6 @@
#include "AccountMgr.h"
#include "CellImpl.h"
#include "CharacterCache.h"
#include "ChatLink.h"
#include "ChatPackets.h"
#include "Common.h"
#include "DatabaseEnv.h"
@@ -37,6 +36,7 @@
#include "World.h"
#include "WorldSession.h"
#include <boost/algorithm/string/replace.hpp>
#include <sstream>
// Lazy loading of the command table cache from commands and the
// ScriptMgr should be thread safe since the player commands,
@@ -445,66 +445,6 @@ bool ChatHandler::ParseCommands(char const* text)
return _ParseCommands(text+1);
}
bool ChatHandler::isValidChatMessage(char const* message)
{
/*
Valid examples:
|cffa335ee|Hitem:812:0:0:0:0:0:0:0:70|h[Glowing Brightwood Staff]|h|r
|cffffff00|Hquest:51101:-1:110:120:5|h[The Wounded King]|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;
// 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)
{
// 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->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY) == 2)
{
if (commandChar == *validSequenceIterator)
{
if (validSequenceIterator == validSequence + 4)
validSequenceIterator = validSequence;
else
++validSequenceIterator;
}
else
return false;
}
}
return true;
}
return LinkExtractor(message).IsValidMessage();
}
bool ChatHandler::ShowHelpForSubCommands(std::vector<ChatCommand> const& table, char const* cmd, char const* subcmd)
{
std::string list;

View File

@@ -76,7 +76,6 @@ class TC_GAME_API ChatHandler
static std::vector<ChatCommand> const& getCommandTable();
static void invalidateCommandTable();
bool isValidChatMessage(const char* msg);
void SendGlobalSysMessage(const char *str);
bool hasStringAbbr(const char* name, const char* part);

View File

@@ -17,7 +17,6 @@
#include "ChatCommandArgs.h"
#include "ChatCommand.h"
#include "ChatCommandHyperlinks.h"
#include "DB2Stores.h"
#include "ObjectMgr.h"
@@ -26,7 +25,7 @@ using namespace Trinity::ChatCommands;
struct AchievementVisitor
{
using value_type = AchievementEntry const*;
value_type operator()(Hyperlink<achievement> achData) const { return achData->achievement; }
value_type operator()(Hyperlink<achievement> achData) const { return achData->Achievement; }
value_type operator()(uint32 achId) const { return sAchievementStore.LookupEntry(achId); }
};
char const* Trinity::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, char const* args)

View File

@@ -20,12 +20,13 @@
#include "ChatCommandHelpers.h"
#include "ChatCommandTags.h"
#include "ChatCommandHyperlinks.h"
struct GameTele;
namespace Trinity {
namespace ChatCommands {
namespace Trinity
{
namespace ChatCommands
{
/************************** ARGUMENT HANDLERS *******************************************\
|* Define how to extract contents of a certain requested type from a string *|
@@ -129,6 +130,7 @@ struct TC_GAME_API ArgInfo<bool>
static char const* TryConsume(bool&, char const*);
};
}}
}
}
#endif

View File

@@ -21,8 +21,10 @@
#include "advstd.h"
#include <type_traits>
namespace Trinity {
namespace ChatCommands {
namespace Trinity
{
namespace ChatCommands
{
static constexpr char COMMAND_DELIMITER = ' ';
@@ -70,6 +72,7 @@ struct get_nth<0, T1, Ts...>
template <size_t index, typename... Ts>
using get_nth_t = typename get_nth<index, Ts...>::type;
}}
}
}
#endif

View File

@@ -1,64 +0,0 @@
/*
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ChatCommandHyperlinks.h"
#include "DB2Stores.h"
static constexpr char HYPERLINK_DELIMITER = ':';
class AchievementLinkTokenizer
{
public:
AchievementLinkTokenizer(char const* pos, size_t len) : _pos(pos), _len(len), _empty(false) {}
template <typename T>
bool TryConsumeTo(T& val)
{
if (_empty)
return false;
char const* firstPos = _pos;
size_t thisLen = 0;
// find next delimiter
for (; _len && *_pos != HYPERLINK_DELIMITER; --_len, ++_pos, ++thisLen);
if (_len)
--_len, ++_pos; // skip the delimiter
else
_empty = true;
return Trinity::ChatCommands::base_tag::StoreTo(val, firstPos, thisLen);
}
bool IsEmpty() { return _empty; }
private:
char const* _pos;
size_t _len;
bool _empty;
};
bool Trinity::ChatCommands::achievement::StoreTo(AchievementLinkData& val, char const* pos, size_t len)
{
AchievementLinkTokenizer t(pos, len);
uint32 achievementId;
if (!t.TryConsumeTo(achievementId))
return false;
val.achievement = sAchievementStore.LookupEntry(achievementId);
return val.achievement && t.TryConsumeTo(val.characterId) && t.TryConsumeTo(val.isFinished) &&
t.TryConsumeTo(val.month) && t.TryConsumeTo(val.day) && t.TryConsumeTo(val.year) && t.TryConsumeTo(val.criteria[0]) &&
t.TryConsumeTo(val.criteria[1]) && t.TryConsumeTo(val.criteria[2]) && t.TryConsumeTo(val.criteria[3]) && t.IsEmpty();
}

View File

@@ -1,160 +0,0 @@
/*
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRINITY_CHATCOMMANDHYPERLINKS_H
#define TRINITY_CHATCOMMANDHYPERLINKS_H
#include "ChatCommandHelpers.h"
#include "ChatCommandTags.h"
#include "ObjectGuid.h"
struct AchievementEntry;
namespace Trinity {
namespace ChatCommands {
// for details on what ContainerTag has to expose, see ChatCommandTags.h
template <typename linktag>
struct Hyperlink : public ContainerTag
{
typedef typename linktag::value_type value_type;
typedef advstd::remove_cvref_t<value_type> storage_type;
public:
operator value_type() const { return val; }
value_type const& operator*() const { return val; }
value_type const* operator->() const { return &val; }
char const* TryConsume(char const* pos)
{
//color tag
if (*(pos++) != '|' || *(pos++) != 'c')
return nullptr;
for (uint8 i = 0; i < 8; ++i)
if (!*(pos++)) // make sure we don't overrun a terminator
return nullptr;
// link data start tag
if (*(pos++) != '|' || *(pos++) != 'H')
return nullptr;
// link tag, should match argument
char const* tag = linktag::tag();
while (*tag)
if (*(pos++) != *(tag++))
return nullptr;
// separator
if (*(pos++) != ':')
return nullptr;
// ok, link data, let's figure out how long it is
char const* datastart = pos;
size_t datalength = 0;
while (*pos && *(pos++) != '|')
++datalength;
// ok, next should be link data end tag...
if (*(pos++) != 'h')
return nullptr;
// then visible link text, skip to next '|', should be '|h|r' terminator
while (*pos && *(pos++) != '|');
if (*(pos++) != 'h' || *(pos++) != '|' || *(pos++) != 'r')
return nullptr;
// finally, skip to end of token
tokenize(pos);
// store value
if (!linktag::StoreTo(val, datastart, datalength))
return nullptr;
// return final pos
return pos;
}
private:
storage_type val;
};
/************************** LINK TAGS ***************************************************\
|* Link tags must abide by the following: *|
|* - MUST expose ::value_type typedef *|
|* - storage type is remove_cvref_t<value_type> *|
|* - MUST expose static ::tag method, void -> const char* *|
|* - this method SHOULD be constexpr *|
|* - returns identifier string for the link ("creature", "creature_entry", "item") *|
|* - MUST expose static ::StoreTo method, (storage&, char const*, size_t) *|
|* - assign value_type& based on content of std::string(char const*, size_t) *|
|* - return value indicates success/failure *|
|* - for integral/string types this can be achieved by extending base_tag *|
\****************************************************************************************/
struct base_tag
{
static bool StoreTo(std::string& val, char const* pos, size_t len)
{
val.assign(pos, len);
return true;
}
static bool StoreTo(ObjectGuid& val, char const* pos, size_t len)
{
val = ObjectGuid::FromString(std::string(pos, len));
return true;
}
template <typename T>
static std::enable_if_t<advstd::is_integral_v<T> && advstd::is_unsigned_v<T>, bool> StoreTo(T& val, char const* pos, size_t len)
{
try { val = std::stoull(std::string(pos, len)); }
catch (...) { return false; }
return true;
}
template <typename T>
static std::enable_if_t<advstd::is_integral_v<T> && advstd::is_signed_v<T>, bool> StoreTo(T& val, char const* pos, size_t len)
{
try { val = std::stoll(std::string(pos, len)); }
catch (...) { return false; }
return true;
}
};
#define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr char const* tag() { return #ltag; } }
make_base_tag(areatrigger, uint32);
make_base_tag(creature, ObjectGuid::LowType);
make_base_tag(creature_entry, uint32);
make_base_tag(gameobject, ObjectGuid::LowType);
make_base_tag(taxinode, uint32);
make_base_tag(tele, uint32);
#undef make_base_tag
struct AchievementLinkData
{
AchievementEntry const* achievement;
ObjectGuid characterId;
bool isFinished;
uint16 year;
uint8 month;
uint8 day;
uint32 criteria[4];
};
struct TC_GAME_API achievement
{
using value_type = AchievementLinkData;
static constexpr char const* tag() { return "achievement"; }
static bool StoreTo(AchievementLinkData& val, char const* pos, size_t len);
};
}}
#endif

View File

@@ -19,6 +19,7 @@
#define TRINITY_CHATCOMMANDTAGS_H
#include "ChatCommandHelpers.h"
#include "Hyperlinks.h"
#include "Optional.h"
#include <boost/variant.hpp>
#include <cmath>
@@ -28,8 +29,10 @@
#include <type_traits>
#include <utility>
namespace Trinity {
namespace ChatCommands {
namespace Trinity
{
namespace ChatCommands
{
/************************** CONTAINER TAGS **********************************************\
|* Simple holder classes to differentiate between extraction methods *|
|* Should inherit from ContainerTag for template identification *|
@@ -75,6 +78,49 @@ struct ExactSequence : public ContainerTag
char const* TryConsume(char const* pos) const { return ExactSequence::_TryConsume(pos); }
};
template <typename linktag>
struct Hyperlink : public ContainerTag
{
typedef typename linktag::value_type value_type;
typedef advstd::remove_cvref_t<value_type> storage_type;
public:
operator value_type() const { return val; }
value_type operator*() const { return val; }
storage_type const* operator->() const { return &val; }
char const* TryConsume(char const* pos)
{
Trinity::Hyperlinks::HyperlinkInfo info = Trinity::Hyperlinks::ParseHyperlink(pos);
// invalid hyperlinks cannot be consumed
if (!info)
return nullptr;
// check if we got the right tag
if (info.tag.second != strlen(linktag::tag()))
return nullptr;
if (strncmp(info.tag.first, linktag::tag(), strlen(linktag::tag())) != 0)
return nullptr;
// store value
if (!linktag::StoreTo(val, info.data.first, info.data.second))
return nullptr;
// finally, skip to end of token
pos = info.next;
tokenize(pos);
// return final pos
return pos;
}
private:
storage_type val;
};
// pull in link tags for user convenience
using namespace ::Trinity::Hyperlinks::LinkTags;
/************************** VARIANT TAG LOGIC *********************************\
|* This has some special handling over in ChatCommand.h *|
\******************************************************************************/
@@ -118,6 +164,7 @@ struct Variant : public boost::variant<T1, Ts...>
decltype(auto) get() const { return boost::get<get_nth_t<index, T1, Ts...>>(*this); }
};
}}
}
}
#endif

View File

@@ -1,947 +0,0 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ChatLink.h"
#include "AchievementMgr.h"
#include "DB2Stores.h"
#include "Item.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "QuestDef.h"
#include "SpellInfo.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
// - 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:min_level:max_level:scaling_faction|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
inline bool ReadUInt32(std::istringstream& iss, uint32& res)
{
iss >> std::dec >> res;
return !iss.fail() && !iss.eof();
}
inline bool ReadInt32(std::istringstream& iss, int32& res)
{
iss >> std::dec >> res;
return !iss.fail() && !iss.eof();
}
inline std::string ReadSkip(std::istringstream& iss, char term)
{
std::string res;
char c = iss.peek();
while (c != term && c != '\0')
{
res += c;
iss.ignore(1);
c = iss.peek();
}
return res;
}
inline bool CheckDelimiter(std::istringstream& iss, char delimiter, char const* context)
{
char c = iss.peek();
if (c != delimiter)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): invalid %s link structure ('%c' expected, '%c' found)", iss.str().c_str(), context, delimiter, c);
return false;
}
iss.ignore(1);
return true;
}
inline bool ReadHex(std::istringstream& iss, uint32& res, uint32 length)
{
std::istringstream::pos_type pos = iss.tellg();
iss >> std::hex >> res;
//uint32 size = uint32(iss.gcount());
if (length && uint32(iss.tellg() - pos) != length)
return false;
return !iss.fail() && !iss.eof();
}
#define DELIMITER ':'
#define PIPE_CHAR '|'
bool ChatLink::ValidateName(char* buffer, char const* /*context*/)
{
_name = buffer;
return true;
}
// |color|Hitem:item_id:perm_ench_id:gem1:gem2:gem3:0:random_property:property_seed:reporter_level:reporter_spec:modifiers_mask:context:numBonusListIDs:bonusListIDs(%d):numModifiers:(modifierType(%d):modifierValue(%d)):gem1numBonusListIDs:gem1bonusListIDs(%d):gem2numBonusListIDs:gem2bonusListIDs(%d):gem3numBonusListIDs:gem3bonusListIDs(%d):creator:use_enchant_id|h[name]|h|r
// |cffa335ee|Hitem:124382:0:0:0:0:0:0:0:0:0:0:0:4:42:562:565:567|h[Edict of Argus]|h|r");
bool ItemChatLink::Initialize(std::istringstream& iss)
{
// Read item entry
uint32 itemEntry = 0;
if (!ReadUInt32(iss, itemEntry))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item entry", iss.str().c_str());
return false;
}
// Validate item
_item = sObjectMgr->GetItemTemplate(itemEntry);
if (!_item)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid itemEntry %u in |item command", iss.str().c_str(), itemEntry);
return false;
}
// Validate item's color
uint32 colorQuality = _item->GetQuality();
if (_item->GetFlags3() & ITEM_FLAG3_DISPLAY_AS_HEIRLOOM)
colorQuality = ITEM_QUALITY_HEIRLOOM;
if (_color != ItemQualityColors[colorQuality])
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): linked item has color %u, but user claims %u", iss.str().c_str(), ItemQualityColors[colorQuality], _color);
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
if (HasValue(iss) && !ReadInt32(iss, _enchantId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item enchantId", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
if (HasValue(iss) && !ReadInt32(iss, _gemItemId[0]))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item gem id 1", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
if (HasValue(iss) && !ReadInt32(iss, _gemItemId[1]))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item gem id 2", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
if (HasValue(iss) && !ReadInt32(iss, _gemItemId[2]))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item gem id 3", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
int32 zero = 0;
if (HasValue(iss) && !ReadInt32(iss, zero))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading zero", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
if (HasValue(iss) && !ReadInt32(iss, zero))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item random property id", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
if (HasValue(iss) && !ReadInt32(iss, zero))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item random property seed", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
if (HasValue(iss) && !ReadInt32(iss, _reporterLevel))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item owner level", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
if (HasValue(iss) && !ReadInt32(iss, _reporterSpec))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item owner spec", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
int32 modifiersMask = 0;
if (HasValue(iss) && !ReadInt32(iss, modifiersMask))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item modifiers mask", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
if (HasValue(iss) && !ReadInt32(iss, _context))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item context", iss.str().c_str());
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
uint32 numBonusListIDs = 0;
if (HasValue(iss) && !ReadUInt32(iss, numBonusListIDs))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item bonus lists size", iss.str().c_str());
return false;
}
uint32 const maxBonusListIDs = 16;
if (numBonusListIDs > maxBonusListIDs)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): too many item bonus list IDs %u in |item command", iss.str().c_str(), numBonusListIDs);
return false;
}
_bonusListIDs.resize(numBonusListIDs);
for (uint32 index = 0; index < numBonusListIDs; ++index)
{
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
int32 id = 0;
if (!ReadInt32(iss, id))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item bonus list id (index %u)", iss.str().c_str(), index);
return false;
}
if (!sDB2Manager.GetItemBonusList(id))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid item bonus list id %d in |item command", iss.str().c_str(), id);
return false;
}
_bonusListIDs[index] = id;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
uint32 numModifiers = 0;
if (HasValue(iss) && !ReadUInt32(iss, numModifiers))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item modifiers size", iss.str().c_str());
return false;
}
if (numModifiers > MAX_ITEM_MODIFIERS)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): too many item modifiers %u in |item command", iss.str().c_str(), numBonusListIDs);
return false;
}
for (uint32 i = 0; i < numModifiers; ++i)
{
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
int32 type = 0;
if (!ReadInt32(iss, type))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item modifier type (index %u)", iss.str().c_str(), i);
return false;
}
if (type > MAX_ITEM_MODIFIERS)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): invalid item modifier type %u (index %u)", iss.str().c_str(), type, i);
return false;
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
int32 id = 0;
if (!ReadInt32(iss, id))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item modifier value (index %u)", iss.str().c_str(), i);
return false;
}
_modifiers.emplace_back(type, id);
}
for (uint32 i = 0; i < MAX_ITEM_PROTO_SOCKETS; ++i)
{
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
numBonusListIDs = 0;
if (HasValue(iss) && !ReadUInt32(iss, numBonusListIDs))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item bonus lists size for gem %u", iss.str().c_str(), i);
return false;
}
if (numBonusListIDs > maxBonusListIDs)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): too many item bonus list IDs %u in |item command for gem %u", iss.str().c_str(), numBonusListIDs, i);
return false;
}
_gemBonusListIDs[i].resize(numBonusListIDs);
for (uint32 index = 0; index < numBonusListIDs; ++index)
{
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
int32 id = 0;
if (!ReadInt32(iss, id))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item bonus list id (index %u) for gem %u", iss.str().c_str(), index, i);
return false;
}
if (!sDB2Manager.GetItemBonusList(id))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid item bonus list id %d in |item command for gem %u", iss.str().c_str(), id, i);
return false;
}
_gemBonusListIDs[i][index] = id;
}
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
// guid as string
if (HasValue(iss))
{
std::array<char, 128> guidBuffer = { };
if (!iss.getline(guidBuffer.data(), 128, DELIMITER))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading creator guid string", iss.str().c_str());
return false;
}
iss.unget(); // put next : back into stream
}
if (!CheckDelimiter(iss, DELIMITER, "item"))
return false;
if (HasValue(iss) && !ReadInt32(iss, _useEnchantId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading on use enchatment id", iss.str().c_str());
return false;
}
return true;
}
std::string ItemChatLink::FormatName(uint8 index, LocalizedString* suffixStrings) const
{
std::stringstream ss;
ss << _item->GetName(LocaleConstant(index));
if (!(_item->GetFlags3() & ITEM_FLAG3_HIDE_NAME_SUFFIX))
if (suffixStrings)
ss << ' ' << suffixStrings->Str[index];
return ss.str();
}
// item links are compacted to remove all zero values
bool ItemChatLink::HasValue(std::istringstream& iss) const
{
char next = iss.peek();
return next != DELIMITER && next != PIPE_CHAR;
}
bool ItemChatLink::ValidateName(char* buffer, char const* context)
{
ChatLink::ValidateName(buffer, context);
// TODO: use suffix from ItemNameDescription
LocalizedString* suffixStrings = nullptr;
for (uint8 locale = LOCALE_enUS; locale < TOTAL_LOCALES; ++locale)
{
if (locale == LOCALE_none)
continue;
if (FormatName(locale, suffixStrings) == buffer)
return true;
}
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): linked item (id: %u) name wasn't found in any localization", context, _item->GetId());
return false;
}
// |color|Hquest:quest_id:quest_level:min_level:max_level:scaling_faction|h[name]|h|r
// |cffffff00|Hquest:51101:-1:110:120:5|h[The Wounded King]|h|r
bool QuestChatLink::Initialize(std::istringstream& iss)
{
// Read quest id
uint32 questId = 0;
if (!ReadUInt32(iss, questId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading quest entry", iss.str().c_str());
return false;
}
// Validate quest
_quest = sObjectMgr->GetQuestTemplate(questId);
if (!_quest)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): quest template %u not found", iss.str().c_str(), questId);
return false;
}
// Check delimiter
if (!CheckDelimiter(iss, DELIMITER, "quest"))
return false;
// Read quest level
if (!ReadInt32(iss, _questLevel))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading quest level", iss.str().c_str());
return false;
}
// Validate quest level
if (_questLevel >= STRONG_MAX_LEVEL)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): quest level %d is too big", iss.str().c_str(), _questLevel);
return false;
}
if (!ReadInt32(iss, _minLevel))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading quest min level", iss.str().c_str());
return false;
}
if (!ReadInt32(iss, _maxLevel))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading quest max level", iss.str().c_str());
return false;
}
if (!ReadInt32(iss, _scalingFaction))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading quest scaling faction", iss.str().c_str());
return false;
}
return true;
}
bool QuestChatLink::ValidateName(char* buffer, char const* context)
{
ChatLink::ValidateName(buffer, context);
bool res = (_quest->GetLogTitle() == buffer);
if (!res)
if (QuestTemplateLocale const* ql = sObjectMgr->GetQuestLocale(_quest->GetQuestId()))
for (uint8 i = 0; i < ql->LogTitle.size(); i++)
if (ql->LogTitle[i] == buffer)
{
res = true;
break;
}
if (!res)
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): linked quest (id: %u) title wasn't found in any localization", context, _quest->GetQuestId());
return res;
}
// |color|Hspell:spell_id|h[name]|h|r
// |cff71d5ff|Hspell:21563|h[Command]|h|r
bool SpellChatLink::Initialize(std::istringstream& iss)
{
if (_color != CHAT_LINK_COLOR_SPELL)
return false;
// Read spell id
uint32 spellId = 0;
if (!ReadUInt32(iss, spellId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading spell entry", iss.str().c_str());
return false;
}
// Validate spell
_spell = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
if (!_spell)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |spell command", iss.str().c_str(), spellId);
return false;
}
return true;
}
bool SpellChatLink::ValidateName(char* buffer, char const* context)
{
ChatLink::ValidateName(buffer, context);
// spells with that flag have a prefix of "$PROFESSION: "
if (_spell->HasAttribute(SPELL_ATTR0_TRADESPELL))
{
SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(_spell->Id);
if (bounds.first == bounds.second)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): skill line not found for spell %u", context, _spell->Id);
return false;
}
SkillLineAbilityEntry const* skillInfo = bounds.first->second;
if (!skillInfo)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): skill line ability not found for spell %u", context, _spell->Id);
return false;
}
SkillLineEntry const* skillLine = sSkillLineStore.LookupEntry(skillInfo->SkillLine);
if (!skillLine)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): skill line not found for skill %u", context, skillInfo->SkillLine);
return false;
}
for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1))
{
uint32 skillLineNameLength = strlen(skillLine->DisplayName[i]);
if (skillLineNameLength > 0 && strncmp(skillLine->DisplayName[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);
break;
}
}
}
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
if (*_spell->SpellName->Str[i] && strcmp(_spell->SpellName->Str[i], buffer) == 0)
return true;
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): linked spell (id: %u) name wasn't found in any localization", context, _spell->Id);
return false;
}
// |color|Hachievement:achievement_id:player_guid:0:0:0:0:0:0:0:0|h[name]|h|r
// |cffffff00|Hachievement:546:0000000000000001:0:0:0:-1:0:0:0:0|h[Safe Deposit]|h|r
bool AchievementChatLink::Initialize(std::istringstream& iss)
{
if (_color != CHAT_LINK_COLOR_ACHIEVEMENT)
return false;
// Read achievemnt Id
uint32 achievementId = 0;
if (!ReadUInt32(iss, achievementId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading achievement entry", iss.str().c_str());
return false;
}
// Validate achievement
_achievement = sAchievementStore.LookupEntry(achievementId);
if (!_achievement)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid achivement id %u in |achievement command", iss.str().c_str(), achievementId);
return false;
}
// Check delimiter
if (!CheckDelimiter(iss, DELIMITER, "achievement"))
return false;
// Read HEX
if (!ReadHex(iss, _guid, 0))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): invalid hexadecimal number while reading char's guid", iss.str().c_str());
return false;
}
// Skip progress
const uint8 propsCount = 8;
for (uint8 index = 0; index < propsCount; ++index)
{
if (!CheckDelimiter(iss, DELIMITER, "achievement"))
return false;
if (!ReadUInt32(iss, _data[index]))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading achievement property (%u)", iss.str().c_str(), index);
return false;
}
}
return true;
}
bool AchievementChatLink::ValidateName(char* buffer, char const* context)
{
ChatLink::ValidateName(buffer, context);
for (LocaleConstant locale = LOCALE_enUS; locale < TOTAL_LOCALES; locale = LocaleConstant(locale + 1))
{
if (locale == LOCALE_none)
continue;
if (strcmp(_achievement->Title[locale], buffer) == 0)
return true;
}
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): linked achievement (id: %u) name wasn't found in any localization", context, _achievement->ID);
return false;
}
// |color|Htrade:spell_id:cur_value:max_value:player_guid:base64_data|h[name]|h|r
// |cffffd000|Htrade:4037:1:150:1:6AAAAAAAAAAAAAAAAAAAAAAOAADAAAAAAAAAAAAAAAAIAAAAAAAAA|h[Engineering]|h|r
bool TradeChatLink::Initialize(std::istringstream& iss)
{
if (_color != CHAT_LINK_COLOR_TRADE)
return false;
// Spell Id
uint32 spellId = 0;
if (!ReadUInt32(iss, spellId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading achievement entry", iss.str().c_str());
return false;
}
// Validate spell
_spell = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
if (!_spell)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |trade command", iss.str().c_str(), spellId);
return false;
}
// Check delimiter
if (!CheckDelimiter(iss, DELIMITER, "trade"))
return false;
// Minimum talent level
if (!ReadInt32(iss, _minSkillLevel))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading minimum talent level", iss.str().c_str());
return false;
}
// Check delimiter
if (!CheckDelimiter(iss, DELIMITER, "trade"))
return false;
// Maximum talent level
if (!ReadInt32(iss, _maxSkillLevel))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading maximum talent level", iss.str().c_str());
return false;
}
// Check delimiter
if (!CheckDelimiter(iss, DELIMITER, "trade"))
return false;
// Something hexadecimal
if (!ReadHex(iss, _guid, 0))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading achievement's owner guid", iss.str().c_str());
return false;
}
// Skip base64 encoded stuff
_base64 = ReadSkip(iss, PIPE_CHAR);
return true;
}
// |color|Htalent:talent_id:rank|h[name]|h|r
// |cff4e96f7|Htalent:2232:-1|h[Taste for Blood]|h|r
bool TalentChatLink::Initialize(std::istringstream& iss)
{
if (_color != CHAT_LINK_COLOR_TALENT)
return false;
// Read talent entry
if (!ReadUInt32(iss, _talentId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading talent entry", iss.str().c_str());
return false;
}
// Validate talent
TalentEntry const* talentInfo = sTalentStore.LookupEntry(_talentId);
if (!talentInfo)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid talent id %u in |talent command", iss.str().c_str(), _talentId);
return false;
}
// Validate talent's spell
_spell = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE);
if (!_spell)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |trade command", iss.str().c_str(), talentInfo->SpellID);
return false;
}
// Delimiter
if (!CheckDelimiter(iss, DELIMITER, "talent"))
return false;
// Rank
if (!ReadInt32(iss, _rankId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading talent rank", iss.str().c_str());
return false;
}
return true;
}
// |color|Henchant:recipe_spell_id|h[prof_name: recipe_name]|h|r
// |cffffd000|Henchant:3919|h[Engineering: Rough Dynamite]|h|r
bool EnchantmentChatLink::Initialize(std::istringstream& iss)
{
if (_color != CHAT_LINK_COLOR_ENCHANT)
return false;
// Spell Id
uint32 spellId = 0;
if (!ReadUInt32(iss, spellId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading enchantment spell entry", iss.str().c_str());
return false;
}
// Validate spell
_spell = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
if (!_spell)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |enchant command", iss.str().c_str(), spellId);
return false;
}
return true;
}
// |color|Hglyph:glyph_slot_id:glyph_prop_id|h[%s]|h|r
// |cff66bbff|Hglyph:21:762|h[Glyph of Bladestorm]|h|r
bool GlyphChatLink::Initialize(std::istringstream& iss)
{
if (_color != CHAT_LINK_COLOR_GLYPH)
return false;
// Slot
if (!ReadUInt32(iss, _slotId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading slot id", iss.str().c_str());
return false;
}
// Check delimiter
if (!CheckDelimiter(iss, DELIMITER, "glyph"))
return false;
// Glyph Id
uint32 glyphId = 0;
if (!ReadUInt32(iss, glyphId))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading glyph entry", iss.str().c_str());
return false;
}
// Validate glyph
_glyph = sGlyphPropertiesStore.LookupEntry(glyphId);
if (!_glyph)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid glyph id %u in |glyph command", iss.str().c_str(), glyphId);
return false;
}
// Validate glyph's spell
_spell = sSpellMgr->GetSpellInfo(_glyph->SpellID, DIFFICULTY_NONE);
if (!_spell)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |glyph command", iss.str().c_str(), _glyph->SpellID);
return false;
}
return true;
}
LinkExtractor::LinkExtractor(char const* msg) : _iss(msg) { }
LinkExtractor::~LinkExtractor()
{
for (Links::iterator itr = _links.begin(); itr != _links.end(); ++itr)
delete *itr;
_links.clear();
}
bool LinkExtractor::IsValidMessage()
{
const char validSequence[6] = "cHhhr";
char const* validSequenceIterator = validSequence;
char buffer[256];
std::istringstream::pos_type startPos = 0;
uint32 color = 0;
ChatLink* link = nullptr;
while (!_iss.eof())
{
if (validSequence == validSequenceIterator)
{
link = nullptr;
_iss.ignore(255, PIPE_CHAR);
startPos = _iss.tellg() - std::istringstream::pos_type(1);
}
else if (_iss.get() != PIPE_CHAR)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence aborted unexpectedly", _iss.str().c_str());
return false;
}
// pipe has always to be followed by at least one char
if (_iss.peek() == '\0')
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): pipe followed by '\\0'", _iss.str().c_str());
return false;
}
// no further pipe commands
if (_iss.eof())
break;
char commandChar;
_iss.get(commandChar);
// | in normal messages is escaped by ||
if (commandChar != PIPE_CHAR)
{
if (commandChar == *validSequenceIterator)
{
if (validSequenceIterator == validSequence+4)
validSequenceIterator = validSequence;
else
++validSequenceIterator;
}
else
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): invalid sequence, expected '%c' but got '%c'", _iss.str().c_str(), *validSequenceIterator, commandChar);
return false;
}
}
else if (validSequence != validSequenceIterator)
{
// no escaped pipes in sequences
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got escaped pipe in sequence", _iss.str().c_str());
return false;
}
switch (commandChar)
{
case 'c':
if (!ReadHex(_iss, color, 8))
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): invalid hexadecimal number while reading color", _iss.str().c_str());
return false;
}
break;
case 'H':
// read chars up to colon = link type
_iss.getline(buffer, 256, DELIMITER);
if (_iss.eof())
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", _iss.str().c_str());
return false;
}
if (strcmp(buffer, "item") == 0)
link = new ItemChatLink();
else if (strcmp(buffer, "quest") == 0)
link = new QuestChatLink();
else if (strcmp(buffer, "trade") == 0)
link = new TradeChatLink();
else if (strcmp(buffer, "talent") == 0)
link = new TalentChatLink();
else if (strcmp(buffer, "spell") == 0)
link = new SpellChatLink();
else if (strcmp(buffer, "enchant") == 0)
link = new EnchantmentChatLink();
else if (strcmp(buffer, "achievement") == 0)
link = new AchievementChatLink();
else if (strcmp(buffer, "glyph") == 0)
link = new GlyphChatLink();
else
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): user sent unsupported link type '%s'", _iss.str().c_str(), buffer);
return false;
}
_links.push_back(link);
link->SetColor(color);
if (!link->Initialize(_iss))
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 (_iss.get() != '[')
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): link caption doesn't start with '['", _iss.str().c_str());
return false;
}
_iss.getline(buffer, 256, ']');
if (_iss.eof())
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", _iss.str().c_str());
return false;
}
if (!link)
return false;
if (!link->ValidateName(buffer, _iss.str().c_str()))
return false;
}
break;
case 'r':
if (link)
link->SetBounds(startPos, _iss.tellg());
case '|':
// no further payload
break;
default:
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid command |%c", _iss.str().c_str(), commandChar);
return false;
}
}
// check if every opened sequence was also closed properly
if (validSequence != validSequenceIterator)
{
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): EOF in active sequence", _iss.str().c_str());
return false;
}
return true;
}

View File

@@ -1,189 +0,0 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRINITYCORE_CHATLINK_H
#define TRINITYCORE_CHATLINK_H
#include "SharedDefines.h"
#include "Common.h"
#include <list>
#include <sstream>
#include <vector>
#include <cstring>
struct ItemLocale;
struct ItemTemplate;
struct ItemRandomSuffixEntry;
struct ItemRandomPropertiesEntry;
class SpellInfo;
struct AchievementEntry;
struct GlyphPropertiesEntry;
class Quest;
///////////////////////////////////////////////////////////////////////////////////////////////////
// ChatLink - abstract base class for various links
class TC_GAME_API ChatLink
{
public:
ChatLink() : _color(0), _startPos(0), _endPos(0) { }
virtual ~ChatLink() { }
void SetColor(uint32 color) { _color = color; }
// This will allow to extract the whole link string from the message, if necessary.
void SetBounds(std::istringstream::pos_type startPos, std::istringstream::pos_type endPos) { _startPos = startPos; _endPos = endPos; }
virtual bool Initialize(std::istringstream& iss) = 0;
virtual bool ValidateName(char* buffer, const char* context) = 0;
protected:
uint32 _color;
std::string _name;
std::istringstream::pos_type _startPos;
std::istringstream::pos_type _endPos;
};
// ItemChatLink - link to item
class TC_GAME_API ItemChatLink : public ChatLink
{
public:
ItemChatLink() : ChatLink(), _item(nullptr), _enchantId(0), _reporterLevel(0), _reporterSpec(0), _context(0)
{
memset(_gemItemId, 0, sizeof(_gemItemId));
}
virtual bool Initialize(std::istringstream& iss) override;
virtual bool ValidateName(char* buffer, const char* context) override;
protected:
std::string FormatName(uint8 index, LocalizedString* suffixStrings) const;
bool HasValue(std::istringstream& iss) const;
ItemTemplate const* _item;
int32 _enchantId;
int32 _gemItemId[3];
int32 _reporterLevel;
int32 _reporterSpec;
int32 _context;
int32 _useEnchantId;
std::vector<int32> _bonusListIDs;
std::vector<std::pair<uint32, int32>> _modifiers;
std::vector<int32> _gemBonusListIDs[3];
};
// QuestChatLink - link to quest
class TC_GAME_API QuestChatLink : public ChatLink
{
public:
QuestChatLink() : ChatLink(), _quest(nullptr), _questLevel(0), _minLevel(0), _maxLevel(0), _scalingFaction(0) { }
virtual bool Initialize(std::istringstream& iss) override;
virtual bool ValidateName(char* buffer, const char* context) override;
protected:
Quest const* _quest;
int32 _questLevel;
int32 _minLevel;
int32 _maxLevel;
int32 _scalingFaction;
};
// SpellChatLink - link to quest
class TC_GAME_API SpellChatLink : public ChatLink
{
public:
SpellChatLink() : ChatLink(), _spell(nullptr) { }
virtual bool Initialize(std::istringstream& iss) override;
virtual bool ValidateName(char* buffer, const char* context) override;
protected:
SpellInfo const* _spell;
};
// AchievementChatLink - link to quest
class TC_GAME_API AchievementChatLink : public ChatLink
{
public:
AchievementChatLink() : ChatLink(), _guid(0), _achievement(nullptr)
{
memset(_data, 0, sizeof(_data));
}
virtual bool Initialize(std::istringstream& iss) override;
virtual bool ValidateName(char* buffer, const char* context) override;
protected:
uint32 _guid;
AchievementEntry const* _achievement;
uint32 _data[8];
};
// TradeChatLink - link to trade info
class TC_GAME_API TradeChatLink : public SpellChatLink
{
public:
TradeChatLink() : SpellChatLink(), _minSkillLevel(0), _maxSkillLevel(0), _guid(0) { }
virtual bool Initialize(std::istringstream& iss) override;
private:
int32 _minSkillLevel;
int32 _maxSkillLevel;
uint32 _guid;
std::string _base64;
};
// TalentChatLink - link to talent
class TC_GAME_API TalentChatLink : public SpellChatLink
{
public:
TalentChatLink() : SpellChatLink(), _talentId(0), _rankId(0) { }
virtual bool Initialize(std::istringstream& iss) override;
private:
uint32 _talentId;
int32 _rankId;
};
// EnchantmentChatLink - link to enchantment
class TC_GAME_API EnchantmentChatLink : public SpellChatLink
{
public:
EnchantmentChatLink() : SpellChatLink() { }
virtual bool Initialize(std::istringstream& iss) override;
};
// GlyphChatLink - link to glyph
class TC_GAME_API GlyphChatLink : public SpellChatLink
{
public:
GlyphChatLink() : SpellChatLink(), _slotId(0), _glyph(nullptr) { }
virtual bool Initialize(std::istringstream& iss) override;
private:
uint32 _slotId;
GlyphPropertiesEntry const* _glyph;
};
class TC_GAME_API LinkExtractor
{
public:
explicit LinkExtractor(const char* msg);
~LinkExtractor();
bool IsValidMessage();
private:
typedef std::list<ChatLink*> Links;
Links _links;
std::istringstream _iss;
};
#endif // TRINITYCORE_CHATLINK_H

View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Hyperlinks.h"
#include "DB2Stores.h"
#include "Item.h"
#include "ObjectMgr.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
static constexpr char HYPERLINK_DATA_DELIMITER = ':';
class HyperlinkDataTokenizer
{
public:
HyperlinkDataTokenizer(char const* pos, size_t len, bool allowEmptyTokens = false) : _pos(pos), _len(len), _allowEmptyTokens(allowEmptyTokens), _empty(false) {}
template <typename T>
bool TryConsumeTo(T& val)
{
if (_empty)
return false;
char const* firstPos = _pos;
size_t thisLen = 0;
// find next delimiter
for (; _len && *_pos != HYPERLINK_DATA_DELIMITER; --_len, ++_pos, ++thisLen);
if (_len)
--_len, ++_pos; // skip the delimiter
else
_empty = true;
if (_allowEmptyTokens && !thisLen)
{
val = T();
return true;
}
return Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, firstPos, thisLen);
}
bool IsEmpty() { return _empty; }
private:
char const* _pos;
size_t _len;
bool _allowEmptyTokens;
bool _empty;
};
bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& val, char const* pos, size_t len)
{
HyperlinkDataTokenizer t(pos, len);
uint32 achievementId;
if (!t.TryConsumeTo(achievementId))
return false;
val.Achievement = sAchievementStore.LookupEntry(achievementId);
return val.Achievement && t.TryConsumeTo(val.CharacterId) && t.TryConsumeTo(val.IsFinished) &&
t.TryConsumeTo(val.Month) && t.TryConsumeTo(val.Day) && t.TryConsumeTo(val.Year) && t.TryConsumeTo(val.Criteria[0]) &&
t.TryConsumeTo(val.Criteria[1]) && t.TryConsumeTo(val.Criteria[2]) && t.TryConsumeTo(val.Criteria[3]) && t.IsEmpty();
}
bool Trinity::Hyperlinks::LinkTags::enchant::StoreTo(SpellInfo const*& val, char const* pos, size_t len)
{
HyperlinkDataTokenizer t(pos, len);
uint32 spellId;
if (!(t.TryConsumeTo(spellId) && t.IsEmpty()))
return false;
return (val = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE)) && val->HasAttribute(SPELL_ATTR0_TRADESPELL);
}
bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, char const* pos, size_t len)
{
HyperlinkDataTokenizer t(pos, len, true);
uint32 itemId, dummy, numBonusListIDs;
if (!t.TryConsumeTo(itemId))
return false;
val.Item = sObjectMgr->GetItemTemplate(itemId);
if (!(val.Item && t.TryConsumeTo(val.EnchantId) && t.TryConsumeTo(val.GemItemId[0]) && t.TryConsumeTo(val.GemItemId[1]) &&
t.TryConsumeTo(val.GemItemId[2]) && t.TryConsumeTo(dummy) && !dummy && t.TryConsumeTo(dummy) && !dummy && t.TryConsumeTo(dummy) && !dummy &&
t.TryConsumeTo(val.RenderLevel) && t.TryConsumeTo(val.RenderSpecialization) && t.TryConsumeTo(dummy) && !dummy &&
t.TryConsumeTo(val.Context) && t.TryConsumeTo(numBonusListIDs)))
return false;
constexpr uint32 maxBonusListIDs = 16;
if (numBonusListIDs > maxBonusListIDs)
return false;
BonusData evaluatedBonus;
evaluatedBonus.Initialize(val.Item);
val.ItemBonusListIDs.resize(numBonusListIDs);
for (int32& itemBonusListID : val.ItemBonusListIDs)
{
if (!t.TryConsumeTo(itemBonusListID) || !sDB2Manager.GetItemBonusList(itemBonusListID))
return false;
evaluatedBonus.AddBonusList(itemBonusListID);
}
val.Quality = evaluatedBonus.Quality;
val.Suffix = sItemNameDescriptionStore.LookupEntry(evaluatedBonus.Suffix);
if (evaluatedBonus.Suffix && !val.Suffix)
return false;
uint32 numModifiers;
if (!t.TryConsumeTo(numModifiers))
return false;
if (numModifiers > MAX_ITEM_MODIFIERS)
return false;
val.Modifiers.resize(numModifiers);
for (ItemLinkData::Modifier& modifier : val.Modifiers)
if (!(t.TryConsumeTo(modifier.Type) && modifier.Type < MAX_ITEM_MODIFIERS && t.TryConsumeTo(modifier.Value)))
return false;
for (uint32 i = 0; i < MAX_ITEM_PROTO_SOCKETS; ++i)
{
if (!t.TryConsumeTo(numBonusListIDs) || numBonusListIDs > maxBonusListIDs)
return false;
val.GemItemBonusListIDs[i].resize(numBonusListIDs);
for (int32& itemBonusListID : val.GemItemBonusListIDs[i])
if (!t.TryConsumeTo(itemBonusListID) || !sDB2Manager.GetItemBonusList(itemBonusListID))
return false;
}
return t.TryConsumeTo(val.Creator) && t.TryConsumeTo(val.UseEnchantId) && t.IsEmpty();
}
bool Trinity::Hyperlinks::LinkTags::quest::StoreTo(QuestLinkData& val, char const* pos, size_t len)
{
HyperlinkDataTokenizer t(pos, len);
uint32 questId;
if (!t.TryConsumeTo(questId))
return false;
return (val.Quest = sObjectMgr->GetQuestTemplate(questId)) && t.TryConsumeTo(val.ContentTuningId) && t.IsEmpty();
}
bool Trinity::Hyperlinks::LinkTags::spell::StoreTo(SpellLinkData& val, char const* pos, size_t len)
{
HyperlinkDataTokenizer t(pos, len);
uint32 spellId, glyphPropertiesId;
if (!(t.TryConsumeTo(spellId) && t.TryConsumeTo(glyphPropertiesId) && t.IsEmpty()))
return false;
return !!(val.Spell = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE))
&& (!glyphPropertiesId || !!(val.Glyph = sGlyphPropertiesStore.LookupEntry(glyphPropertiesId)));
}
bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentEntry const*& val, char const* pos, size_t len)
{
HyperlinkDataTokenizer t(pos, len);
uint32 talentId;
if (!(t.TryConsumeTo(talentId) && t.IsEmpty()))
return false;
if (!(val = sTalentStore.LookupEntry(talentId)))
return false;
return true;
}
bool Trinity::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, char const* pos, size_t len)
{
HyperlinkDataTokenizer t(pos, len);
uint32 spellId, skillId;
if (!t.TryConsumeTo(val.Owner) || !t.TryConsumeTo(spellId) || !t.TryConsumeTo(skillId) || !t.IsEmpty())
return false;
val.Spell = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
val.Skill = sSkillLineStore.LookupEntry(skillId);
if (!val.Spell || !val.Spell->HasEffect(SPELL_EFFECT_TRADE_SKILL) || !val.Skill)
return false;
return true;
}

View File

@@ -0,0 +1,383 @@
/*
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Hyperlinks.h"
#include "Common.h"
#include "DB2Stores.h"
#include "Errors.h"
#include "ItemTemplate.h"
#include "ObjectMgr.h"
#include "QuestDef.h"
#include "SharedDefines.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "World.h"
#include "advstd.h"
using namespace Trinity::Hyperlinks;
inline uint8 toHex(char c) { return (c >= '0' && c <= '9') ? c - '0' + 0x10 : (c >= 'a' && c <= 'f') ? c - 'a' + 0x1a : 0x00; }
// Validates a single hyperlink
HyperlinkInfo Trinity::Hyperlinks::ParseHyperlink(char const* pos)
{
//color tag
if (*(pos++) != '|' || *(pos++) != 'c')
return nullptr;
uint32 color = 0;
for (uint8 i = 0; i < 8; ++i)
if (uint8 hex = toHex(*(pos++)))
color = (color << 4) | (hex & 0xf);
// link data start tag
if (*(pos++) != '|' || *(pos++) != 'H')
return nullptr;
// link tag, find next : or |
char const* tagStart = pos;
size_t tagLength = 0;
while (*pos && *pos != '|' && *(pos++) != ':') // we only advance pointer to one past if the last thing is : (not for |), this is intentional!
++tagLength;
// ok, link data, skip to next |
char const* dataStart = pos;
size_t dataLength = 0;
while (*pos && *(pos++) != '|')
++dataLength;
// ok, next should be link data end tag...
if (*(pos++) != 'h')
return nullptr;
// then visible link text, starts with [
if (*(pos++) != '[')
return nullptr;
// skip until we hit the next ], abort on unexpected |
char const* textStart = pos;
size_t textLength = 0;
while (*pos)
{
if (*pos == '|')
return nullptr;
if (*(pos++) == ']')
break;
++textLength;
}
// link end tag
if (*(pos++) != '|' || *(pos++) != 'h' || *(pos++) != '|' || *(pos++) != 'r')
return nullptr;
// ok, valid hyperlink, return info
return { pos, color, tagStart, tagLength, dataStart, dataLength, textStart, textLength };
}
template <typename T>
struct LinkValidator
{
static bool IsTextValid(typename T::value_type, char const*, size_t) { return true; }
static bool IsColorValid(typename T::value_type, HyperlinkColor) { return true; }
};
// str1 is null-terminated, str2 is length-terminated, check if they are exactly equal
static bool equal_with_len(char const* str1, char const* str2, size_t len)
{
if (!*str1)
return false;
while (len && *str1 && *(str1++) == *(str2++))
--len;
return !len && !*str1;
}
template <>
struct LinkValidator<LinkTags::achievement>
{
static bool IsTextValid(AchievementLinkData const& data, char const* pos, size_t len)
{
if (!len)
return false;
for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1))
if (equal_with_len(data.Achievement->Title[i], pos, len))
return true;
return false;
}
static bool IsColorValid(AchievementLinkData const&, HyperlinkColor c)
{
return c == CHAT_LINK_COLOR_ACHIEVEMENT;
}
};
template <>
struct LinkValidator<LinkTags::item>
{
static bool IsTextValid(ItemLinkData const& data, char const* pos, size_t len)
{
LocalizedString const* suffixStrings = nullptr;
if (!(data.Item->GetFlags3() & ITEM_FLAG3_HIDE_NAME_SUFFIX) && data.Suffix)
suffixStrings = &data.Suffix->Description;
for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1))
{
std::string name = data.Item->GetName(i);
if (name.empty())
continue;
if (suffixStrings)
{
if (len > name.length() + 1 &&
(strncmp(name.c_str(), pos, name.length()) == 0) &&
(*(pos + name.length()) == ' ') &&
equal_with_len((*suffixStrings)[i], pos + name.length() + 1, len - name.length() - 1))
return true;
}
else if (equal_with_len(name.c_str(), pos, len))
return true;
}
return false;
}
static bool IsColorValid(ItemLinkData const& data, HyperlinkColor c)
{
return c == ItemQualityColors[data.Quality];
}
};
template <>
struct LinkValidator<LinkTags::quest>
{
static bool IsTextValid(QuestLinkData const& data, char const* pos, size_t len)
{
QuestTemplateLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId());
if (!locale)
return equal_with_len(data.Quest->GetLogTitle().c_str(), pos, len);
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
{
std::string const& name = (i == DEFAULT_LOCALE) ? data.Quest->GetLogTitle() : locale->LogTitle[i];
if (name.empty())
continue;
if (equal_with_len(name.c_str(), pos, len))
return true;
}
return false;
}
static bool IsColorValid(QuestLinkData const&, HyperlinkColor c)
{
for (uint8 i = 0; i < MAX_QUEST_DIFFICULTY; ++i)
if (c == QuestDifficultyColors[i])
return true;
return false;
}
};
template <>
struct LinkValidator<LinkTags::spell>
{
static bool IsTextValid(SpellLinkData const& data, char const* pos, size_t len)
{
return IsTextValid(data.Spell, pos, len);
}
static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len)
{
for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1))
if (equal_with_len((*info->SpellName)[i], pos, len))
return true;
return false;
}
static bool IsColorValid(SpellLinkData const&, HyperlinkColor c)
{
return c == CHAT_LINK_COLOR_SPELL;
}
};
template <>
struct LinkValidator<LinkTags::enchant>
{
static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len)
{
SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(info->Id);
if (bounds.first == bounds.second)
return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len);
SkillLineEntry const* skill = sSkillLineStore.LookupEntry(bounds.first->second->SkillupSkillLineID
? bounds.first->second->SkillupSkillLineID
: bounds.first->second->SkillLine);
if (!skill)
return false;
for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1))
{
char const* skillName = skill->DisplayName[i];
size_t skillLen = strlen(skillName);
if (len > skillLen + 2 && // or of form [Skill Name: Spell Name]
!strncmp(pos, skillName, skillLen) && !strncmp(pos + skillLen, ": ", 2) &&
equal_with_len((*info->SpellName)[i], pos + (skillLen + 2), len - (skillLen + 2)))
return true;
}
return false;
}
static bool IsColorValid(SpellInfo const*, HyperlinkColor c)
{
return c == CHAT_LINK_COLOR_ENCHANT;
}
};
template <>
struct LinkValidator<LinkTags::talent>
{
static bool IsTextValid(TalentEntry const* talent, char const* pos, size_t len)
{
if (SpellInfo const* info = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE))
return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len);
return false;
}
static bool IsColorValid(TalentEntry const*, HyperlinkColor c)
{
return c == CHAT_LINK_COLOR_TALENT;
}
};
template <>
struct LinkValidator<LinkTags::trade>
{
static bool IsTextValid(TradeskillLinkData const& data, char const* pos, size_t len)
{
return LinkValidator<LinkTags::spell>::IsTextValid(data.Spell, pos, len);
}
static bool IsColorValid(TradeskillLinkData const&, HyperlinkColor c)
{
return c == CHAT_LINK_COLOR_TRADE;
}
};
#define TryValidateAs(tagname) \
{ \
ASSERT(!strcmp(LinkTags::tagname::tag(), #tagname)); \
if (info.tag.second == strlen(LinkTags::tagname::tag()) && \
!strncmp(info.tag.first, LinkTags::tagname::tag(), strlen(LinkTags::tagname::tag()))) \
{ \
advstd::remove_cvref_t<typename LinkTags::tagname::value_type> t; \
if (!LinkTags::tagname::StoreTo(t, info.data.first, info.data.second)) \
return false; \
if (!LinkValidator<LinkTags::tagname>::IsColorValid(t, info.color)) \
return false; \
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY)) \
if (!LinkValidator<LinkTags::tagname>::IsTextValid(t, info.text.first, info.text.second)) \
return false; \
return true; \
} \
}
static bool ValidateLinkInfo(HyperlinkInfo const& info)
{
TryValidateAs(achievement);
TryValidateAs(area);
TryValidateAs(areatrigger);
TryValidateAs(creature);
TryValidateAs(creature_entry);
TryValidateAs(enchant);
TryValidateAs(gameevent);
TryValidateAs(gameobject);
TryValidateAs(gameobject_entry);
TryValidateAs(item);
TryValidateAs(itemset);
TryValidateAs(player);
TryValidateAs(quest);
TryValidateAs(skill);
TryValidateAs(spell);
TryValidateAs(talent);
TryValidateAs(taxinode);
TryValidateAs(tele);
TryValidateAs(title);
TryValidateAs(trade);
return false;
}
// Validates all hyperlinks and control sequences contained in str
bool Trinity::Hyperlinks::ValidateLinks(std::string& str)
{
bool allValid = true;
std::string::size_type pos = std::string::npos;
// Step 1: Strip all control sequences except ||, |H, |h, |c and |r
do
{
if ((pos = str.rfind('|', pos)) == std::string::npos)
break;
if (pos && str[pos - 1] == '|')
{
--pos;
continue;
}
char next = str[pos + 1];
if (next == 'H' || next == 'h' || next == 'c' || next == 'r')
continue;
allValid = false;
str.erase(pos, 2);
} while (pos--);
// Step 2: Parse all link sequences
// They look like this: |c<color>|H<linktag>:<linkdata>|h[<linktext>]|h|r
// - <color> is 8 hex characters AARRGGBB
// - <linktag> is arbitrary length [a-z_]
// - <linkdata> is arbitrary length, no | contained
// - <linktext> is printable
pos = 0;
while (pos < str.size() && (pos = str.find('|', pos)) != std::string::npos)
{
if (str[pos + 1] == '|') // this is an escaped pipe character (||)
{
pos += 2;
continue;
}
HyperlinkInfo info = ParseHyperlink(str.c_str() + pos);
if (!info)
{ // cannot be parsed at all, so we'll need to cut it out entirely
// find the next start of a link
std::string::size_type next = str.find("|c", pos + 1);
// then backtrack to the previous return control sequence
std::string::size_type end = str.rfind("|r", next);
if (end == std::string::npos || end <= pos) // there is no potential end tag, remove everything after pos (up to next, if available)
{
if (next == std::string::npos)
str.erase(pos);
else
str.erase(pos, next - pos);
}
else
str.erase(pos, end - pos + 2);
allValid = false;
continue;
}
// ok, link parsed successfully - now validate it based on the tag
if (!ValidateLinkInfo(info))
{
// invalid link info, replace with just text
str.replace(pos, (info.next - str.c_str()) - pos, str, info.text.first - str.c_str(), info.text.second);
allValid = false;
continue;
}
// tag is fine, find the next one
pos = info.next - str.c_str();
}
// all tags validated
return allValid;
}

View File

@@ -0,0 +1,242 @@
/*
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRINITY_HYPERLINKS_H
#define TRINITY_HYPERLINKS_H
#include "advstd.h"
#include "ObjectGuid.h"
#include <string>
#include <utility>
struct AchievementEntry;
struct GlyphPropertiesEntry;
struct ItemNameDescriptionEntry;
struct ItemTemplate;
class Quest;
struct SkillLineEntry;
class SpellInfo;
struct TalentEntry;
namespace Trinity
{
namespace Hyperlinks
{
struct AchievementLinkData
{
AchievementEntry const* Achievement;
ObjectGuid CharacterId;
bool IsFinished;
uint16 Year;
uint8 Month;
uint8 Day;
uint32 Criteria[4];
};
struct ItemLinkData
{
ItemTemplate const* Item;
uint32 EnchantId;
uint32 GemItemId[3];
uint8 RenderLevel;
uint32 RenderSpecialization;
uint8 Context;
std::vector<int32> ItemBonusListIDs;
struct Modifier
{
uint32 Type;
int32 Value;
};
std::vector<Modifier> Modifiers;
std::vector<int32> GemItemBonusListIDs[3];
ObjectGuid Creator;
uint32 UseEnchantId;
uint32 Quality;
ItemNameDescriptionEntry const* Suffix;
};
struct QuestLinkData
{
::Quest const* Quest;
uint32 ContentTuningId;
};
struct SpellLinkData
{
SpellInfo const* Spell;
GlyphPropertiesEntry const* Glyph;
};
struct TradeskillLinkData
{
ObjectGuid Owner;
SpellInfo const* Spell;
SkillLineEntry const* Skill;
};
namespace LinkTags {
/************************** LINK TAGS ***************************************************\
|* Link tags must abide by the following: *|
|* - MUST expose ::value_type typedef *|
|* - storage type is remove_cvref_t<value_type> *|
|* - MUST expose static ::tag method, void -> const char* *|
|* - this method SHOULD be constexpr *|
|* - returns identifier string for the link ("creature", "creature_entry", "item") *|
|* - MUST expose static ::StoreTo method, (storage&, char const*, size_t) *|
|* - assign value_type& based on content of std::string(char const*, size_t) *|
|* - return value indicates success/failure *|
|* - for integral/string types this can be achieved by extending base_tag *|
\****************************************************************************************/
struct base_tag
{
static bool StoreTo(std::string& val, char const* pos, size_t len)
{
val.assign(pos, len);
return true;
}
static bool StoreTo(ObjectGuid& val, char const* pos, size_t len)
{
val = ObjectGuid::FromString(std::string(pos, len));
return true;
}
template <typename T>
static std::enable_if_t<advstd::is_integral_v<T> && advstd::is_unsigned_v<T>, bool> StoreTo(T& val, char const* pos, size_t len)
{
try { val = std::stoull(std::string(pos, len)); }
catch (...) { return false; }
return true;
}
template <typename T>
static std::enable_if_t<advstd::is_integral_v<T> && advstd::is_signed_v<T>, bool> StoreTo(T& val, char const* pos, size_t len)
{
try { val = std::stoll(std::string(pos, len)); }
catch (...) { return false; }
return true;
}
};
#define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr char const* tag() { return #ltag; } }
make_base_tag(area, uint32);
make_base_tag(areatrigger, uint32);
make_base_tag(creature, ObjectGuid::LowType);
make_base_tag(creature_entry, uint32);
make_base_tag(gameevent, uint32);
make_base_tag(gameobject, ObjectGuid::LowType);
make_base_tag(gameobject_entry, uint32);
make_base_tag(itemset, uint32);
make_base_tag(player, std::string const&);
make_base_tag(skill, uint32);
make_base_tag(taxinode, uint32);
make_base_tag(tele, uint32);
make_base_tag(title, uint32);
#undef make_base_tag
struct TC_GAME_API achievement
{
using value_type = AchievementLinkData const&;
static constexpr char const* tag() { return "achievement"; }
static bool StoreTo(AchievementLinkData& val, char const* pos, size_t len);
};
struct TC_GAME_API enchant
{
using value_type = SpellInfo const*;
static constexpr char const* tag() { return "enchant"; }
static bool StoreTo(SpellInfo const*& val, char const* pos, size_t len);
};
struct TC_GAME_API item
{
using value_type = ItemLinkData const&;
static constexpr char const* tag() { return "item"; }
static bool StoreTo(ItemLinkData& val, char const* pos, size_t len);
};
struct TC_GAME_API quest
{
using value_type = QuestLinkData const&;
static constexpr char const* tag() { return "quest"; }
static bool StoreTo(QuestLinkData& val, char const* pos, size_t len);
};
struct TC_GAME_API spell
{
using value_type = SpellLinkData const&;
static constexpr char const* tag() { return "spell"; }
static bool StoreTo(SpellLinkData& val, char const* pos, size_t len);
};
struct TC_GAME_API talent
{
using value_type = TalentEntry const*&;
static constexpr char const* tag() { return "talent"; }
static bool StoreTo(TalentEntry const*& val, char const* pos, size_t len);
};
struct TC_GAME_API trade
{
using value_type = TradeskillLinkData const&;
static constexpr char const* tag() { return "trade"; }
static bool StoreTo(TradeskillLinkData& val, char const* pos, size_t len);
};
}
struct HyperlinkColor
{
HyperlinkColor(uint32 c) : r(c >> 16), g(c >> 8), b(c), a(c >> 24) {}
uint8 r, g, b, a;
bool operator==(uint32 c) const
{
if ((c & 0xff) ^ b)
return false;
if (((c >>= 8) & 0xff) ^ g)
return false;
if (((c >>= 8) & 0xff) ^ r)
return false;
if ((c >>= 8) ^ a)
return false;
return true;
}
};
struct HyperlinkInfo
{
HyperlinkInfo(char const* n = nullptr, uint32 c = 0, char const* tS = nullptr, size_t tL = 0, char const* dS = nullptr, size_t dL = 0, char const* cS = nullptr, size_t cL = 0) :
next(n), color(c), tag(tS, tL), data(dS, dL), text(cS, cL) {}
explicit operator bool() { return next; }
char const* const next;
HyperlinkColor const color;
std::pair<char const*, size_t> const tag;
std::pair<char const*, size_t> const data;
std::pair<char const*, size_t> const text;
};
HyperlinkInfo TC_GAME_API ParseHyperlink(char const* pos);
bool TC_GAME_API ValidateLinks(std::string&);
}
}
#endif

View File

@@ -51,7 +51,6 @@ char const* ItemTemplate::GetName(LocaleConstant locale) const
return ExtendedData->Display[locale];
}
bool ItemTemplate::CanChangeEquipStateInCombat() const
{
switch (GetInventoryType())

View File

@@ -29,6 +29,7 @@
#include "Group.h"
#include "Guild.h"
#include "GuildMgr.h"
#include "Hyperlinks.h"
#include "Language.h"
#include "LanguageMgr.h"
#include "Log.h"
@@ -42,6 +43,37 @@
#include "World.h"
#include "WorldPacket.h"
static void StripInvisibleChars(std::string& str)
{
static std::string const invChars = " \t\7\n";
size_t wpos = 0;
bool space = false;
for (size_t pos = 0; pos < str.size(); ++pos)
{
if (invChars.find(str[pos]) != std::string::npos)
{
if (!space)
{
str[wpos++] = ' ';
space = true;
}
}
else
{
if (wpos != pos)
str[wpos++] = str[pos];
else
++wpos;
space = false;
}
}
if (wpos < str.size())
str.erase(wpos, str.size());
}
void WorldSession::HandleChatMessageOpcode(WorldPackets::Chat::ChatMessage& chatMessage)
{
ChatMsg type;
@@ -174,27 +206,32 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms
return;
}
// Strip invisible characters for non-addon messages
if (sWorld->getBoolConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING))
StripInvisibleChars(msg);
if (msg.empty())
return;
if (ChatHandler(this).ParseCommands(msg.c_str()))
return;
// Strip invisible characters for non-addon messages
if (sWorld->getBoolConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING))
stripLineInvisibleChars(msg);
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY) && !ChatHandler(this).isValidChatMessage(msg.c_str()))
bool validMessage = Trinity::Hyperlinks::ValidateLinks(msg);
if (!validMessage)
{
TC_LOG_ERROR("network", "Player %s (%s) sent a chatmessage with an invalid link: %s", GetPlayer()->GetName().c_str(),
GetPlayer()->GetGUID().ToString().c_str(), msg.c_str());
TC_LOG_ERROR("network", "Player %s (%s) sent a chatmessage with an invalid link - corrected", GetPlayer()->GetName().c_str(),
GetPlayer()->GetGUID().ToString().c_str());
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
{
KickPlayer();
return;
return;
}
}
if (msg.length() > 255)
return;
switch (type)
{
case CHAT_MSG_SAY:
@@ -431,6 +468,22 @@ void WorldSession::HandleChatAddonMessage(ChatMsg type, std::string prefix, std:
if (prefix == AddonChannelCommandHandler::PREFIX && AddonChannelCommandHandler(this).ParseCommands(text.c_str()))
return;
bool validMessage = Trinity::Hyperlinks::ValidateLinks(text);
if (!validMessage)
{
TC_LOG_ERROR("network", "Player %s (%s) sent a chatmessage with an invalid link - corrected", GetPlayer()->GetName().c_str(),
GetPlayer()->GetGUID().ToString().c_str());
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
{
KickPlayer();
return;
}
}
if (text.length() > 255)
return;
switch (type)
{
case CHAT_MSG_GUILD:
@@ -505,6 +558,26 @@ void WorldSession::HandleChatMessageAFKOpcode(WorldPackets::Chat::ChatMessageAFK
if (sender->IsInCombat())
return;
// Strip invisible characters for non-addon messages
if (sWorld->getBoolConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING))
StripInvisibleChars(chatMessageAFK.Text);
bool validMessage = Trinity::Hyperlinks::ValidateLinks(chatMessageAFK.Text);
if (!validMessage)
{
TC_LOG_ERROR("network", "Player %s (%s) sent a chatmessage with an invalid link - corrected", GetPlayer()->GetName().c_str(),
GetPlayer()->GetGUID().ToString().c_str());
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
{
KickPlayer();
return;
}
}
if (chatMessageAFK.Text.length() > 255)
return;
if (sender->HasAura(GM_SILENCE_AURA))
{
SendNotification(GetTrinityString(LANG_GM_SILENCE), sender->GetName().c_str());
@@ -541,6 +614,26 @@ void WorldSession::HandleChatMessageDNDOpcode(WorldPackets::Chat::ChatMessageDND
if (sender->IsInCombat())
return;
// Strip invisible characters for non-addon messages
if (sWorld->getBoolConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING))
StripInvisibleChars(chatMessageDND.Text);
bool validMessage = Trinity::Hyperlinks::ValidateLinks(chatMessageDND.Text);
if (!validMessage)
{
TC_LOG_ERROR("network", "Player %s (%s) sent a chatmessage with an invalid link - corrected", GetPlayer()->GetName().c_str(),
GetPlayer()->GetGUID().ToString().c_str());
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
{
KickPlayer();
return;
}
}
if (chatMessageDND.Text.length() > 255)
return;
if (sender->HasAura(GM_SILENCE_AURA))
{
SendNotification(GetTrinityString(LANG_GM_SILENCE), sender->GetName().c_str());

View File

@@ -343,11 +343,10 @@ enum ItemQualities
ITEM_QUALITY_LEGENDARY = 5, // ORANGE
ITEM_QUALITY_ARTIFACT = 6, // LIGHT YELLOW
ITEM_QUALITY_HEIRLOOM = 7, // LIGHT BLUE
ITEM_QUALITY_WOW_TOKEN = 8 // LIGHT BLUE
ITEM_QUALITY_WOW_TOKEN = 8, // LIGHT BLUE
MAX_ITEM_QUALITY
};
#define MAX_ITEM_QUALITY 9
enum SpellCategory
{
SPELL_CATEGORY_FOOD = 11,
@@ -360,7 +359,7 @@ enum SpellVisualKit
SPELL_VISUAL_KIT_DRINK = 438
};
const uint32 ItemQualityColors[MAX_ITEM_QUALITY] =
uint32 constexpr ItemQualityColors[MAX_ITEM_QUALITY] =
{
0xff9d9d9d, // GREY
0xffffffff, // WHITE
@@ -373,6 +372,16 @@ const uint32 ItemQualityColors[MAX_ITEM_QUALITY] =
0xff00ccff // LIGHT BLUE
};
size_t constexpr MAX_QUEST_DIFFICULTY = 5;
uint32 constexpr QuestDifficultyColors[MAX_QUEST_DIFFICULTY] =
{
0xff40c040,
0xff808080,
0xffffff00,
0xffff8040,
0xffff2020
};
// ***********************************
// Spell Attributes definitions
// ***********************************
@@ -5633,14 +5642,13 @@ enum ChatFlags
CHAT_FLAG_MOBILE = 0x40
};
enum ChatLinkColors
enum ChatLinkColors : uint32
{
CHAT_LINK_COLOR_TRADE = 0xffffd000, // orange
CHAT_LINK_COLOR_TALENT = 0xff4e96f7, // blue
CHAT_LINK_COLOR_SPELL = 0xff71d5ff, // bright blue
CHAT_LINK_COLOR_ENCHANT = 0xffffd000, // orange
CHAT_LINK_COLOR_ACHIEVEMENT = 0xffffff00,
CHAT_LINK_COLOR_GLYPH = 0xff66bbff
CHAT_LINK_COLOR_TRADE = 0xffffd000, // orange
CHAT_LINK_COLOR_TALENT = 0xff71d5ff, // bright blue
CHAT_LINK_COLOR_SPELL = 0xff71d5ff, // bright blue
CHAT_LINK_COLOR_ENCHANT = 0xffffd000, // orange
CHAT_LINK_COLOR_ACHIEVEMENT = 0xffffff00,
};
// Values from ItemPetFood (power of (value-1) used for compare with CreatureFamilyEntry.petDietMask

View File

@@ -1940,11 +1940,8 @@ ChatFakeMessagePreventing = 1
#
# ChatStrictLinkChecking.Severity
# Description: Check chat messages for ingame links to spells, items, quests, etc.
# Default: 0 - (Disabled)
# 1 - (Enabled, Check if only valid pipe commands are used, Prevents posting
# pictures.)
# 2 - (Enabled, Verify that pipe commands are used in a correct order)
# 3 - (Check if color, entry and name don't contradict each other. For this to
# Default: 0 - (Only verify that link format looks valid without checking the text)
# 1 - (Check if color, entry and name don't contradict each other. For this to
# work correctly, please assure that you have extracted locale DBCs of
# every language specific client playing on this server)
@@ -1952,10 +1949,10 @@ ChatStrictLinkChecking.Severity = 0
#
# ChatStrictLinkChecking.Kick
# Description: Defines what should be done if a message is considered to contain invalid
# pipe commands.
# Default: 0 - (Silently ignore message)
# 1 - (Disconnect players who sent malformed messages)
# Description: Defines what should be done if a message containing invalid control characters
# is received.
# Default: 0 - (Process sanitized message normally)
# 1 - (Ignore message and kick player)
ChatStrictLinkChecking.Kick = 0