aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/Utilities/Util.cpp34
-rw-r--r--src/common/Utilities/Util.h2
-rw-r--r--src/server/game/Chat/Chat.cpp62
-rw-r--r--src/server/game/Chat/Chat.h1
-rw-r--r--src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp3
-rw-r--r--src/server/game/Chat/ChatCommands/ChatCommandArgs.h10
-rw-r--r--src/server/game/Chat/ChatCommands/ChatCommandHelpers.h9
-rw-r--r--src/server/game/Chat/ChatCommands/ChatCommandHyperlinks.cpp64
-rw-r--r--src/server/game/Chat/ChatCommands/ChatCommandHyperlinks.h160
-rw-r--r--src/server/game/Chat/ChatCommands/ChatCommandTags.h53
-rw-r--r--src/server/game/Chat/ChatLink.cpp947
-rw-r--r--src/server/game/Chat/ChatLink.h189
-rw-r--r--src/server/game/Chat/HyperlinkTags.cpp187
-rw-r--r--src/server/game/Chat/Hyperlinks.cpp383
-rw-r--r--src/server/game/Chat/Hyperlinks.h242
-rw-r--r--src/server/game/Entities/Item/ItemTemplate.cpp1
-rw-r--r--src/server/game/Handlers/ChatHandler.cpp109
-rw-r--r--src/server/game/Miscellaneous/SharedDefines.h30
-rw-r--r--src/server/worldserver/worldserver.conf.dist15
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