diff options
author | Treeston <treeston.mmoc@gmail.com> | 2018-09-13 00:11:27 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2021-10-31 01:39:04 +0200 |
commit | e506c80a64571530cf7edd947a5b7dc2f35f9d1d (patch) | |
tree | e78a0d480537525c9db0b8ebb9d08f6ce8bdcc6e /src | |
parent | fa7e46a4f3f807b0682aba26c7993d142f842648 (diff) |
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 f27284594b18000a1c098262728fb19fdc63fc6c)
Diffstat (limited to 'src')
19 files changed, 1002 insertions, 1499 deletions
diff --git a/src/common/Utilities/Util.cpp b/src/common/Utilities/Util.cpp index b8550c9a72e..4a4daa33b25 100644 --- a/src/common/Utilities/Util.cpp +++ b/src/common/Utilities/Util.cpp @@ -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) { diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h index 259af5068ff..27dee721d16 100644 --- a/src/common/Utilities/Util.h +++ b/src/common/Utilities/Util.h @@ -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); diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp index 2d180a6b1b9..434dbb67d1e 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -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; diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h index c450d5ebd27..d8db951cb66 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -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); diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp index b00b48fe0a0..20893d9df90 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp @@ -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) diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h index bbdb2565f25..110103ebb83 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h @@ -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 diff --git a/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h b/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h index 42c0a195041..fe6c8d709ca 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h @@ -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 diff --git a/src/server/game/Chat/ChatCommands/ChatCommandHyperlinks.cpp b/src/server/game/Chat/ChatCommands/ChatCommandHyperlinks.cpp deleted file mode 100644 index 53035813769..00000000000 --- a/src/server/game/Chat/ChatCommands/ChatCommandHyperlinks.cpp +++ /dev/null @@ -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(); -} diff --git a/src/server/game/Chat/ChatCommands/ChatCommandHyperlinks.h b/src/server/game/Chat/ChatCommands/ChatCommandHyperlinks.h deleted file mode 100644 index f88b383d578..00000000000 --- a/src/server/game/Chat/ChatCommands/ChatCommandHyperlinks.h +++ /dev/null @@ -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 - diff --git a/src/server/game/Chat/ChatCommands/ChatCommandTags.h b/src/server/game/Chat/ChatCommands/ChatCommandTags.h index c28c4f21a8f..b8fcc0e0062 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandTags.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandTags.h @@ -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 diff --git a/src/server/game/Chat/ChatLink.cpp b/src/server/game/Chat/ChatLink.cpp deleted file mode 100644 index 3e64d71f9f5..00000000000 --- a/src/server/game/Chat/ChatLink.cpp +++ /dev/null @@ -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; -} diff --git a/src/server/game/Chat/ChatLink.h b/src/server/game/Chat/ChatLink.h deleted file mode 100644 index 60be2a61f21..00000000000 --- a/src/server/game/Chat/ChatLink.h +++ /dev/null @@ -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 diff --git a/src/server/game/Chat/HyperlinkTags.cpp b/src/server/game/Chat/HyperlinkTags.cpp new file mode 100644 index 00000000000..194ad874228 --- /dev/null +++ b/src/server/game/Chat/HyperlinkTags.cpp @@ -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; +} diff --git a/src/server/game/Chat/Hyperlinks.cpp b/src/server/game/Chat/Hyperlinks.cpp new file mode 100644 index 00000000000..d71316cb5ba --- /dev/null +++ b/src/server/game/Chat/Hyperlinks.cpp @@ -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; +} diff --git a/src/server/game/Chat/Hyperlinks.h b/src/server/game/Chat/Hyperlinks.h new file mode 100644 index 00000000000..c958ce176cf --- /dev/null +++ b/src/server/game/Chat/Hyperlinks.h @@ -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 diff --git a/src/server/game/Entities/Item/ItemTemplate.cpp b/src/server/game/Entities/Item/ItemTemplate.cpp index 78b4dbe0ea7..2835ce6b742 100644 --- a/src/server/game/Entities/Item/ItemTemplate.cpp +++ b/src/server/game/Entities/Item/ItemTemplate.cpp @@ -51,7 +51,6 @@ char const* ItemTemplate::GetName(LocaleConstant locale) const return ExtendedData->Display[locale]; } - bool ItemTemplate::CanChangeEquipStateInCombat() const { switch (GetInventoryType()) diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index 847197352b6..f6a39ea12e8 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -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,26 +206,31 @@ 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; + } + } + if (msg.length() > 255) return; - } switch (type) { @@ -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()); diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 31ba35a7f3e..517f4e085eb 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -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 diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index a2d603cb433..ca5cbd34d5e 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -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 |