TrinityCore
Loading...
Searching...
No Matches
Hyperlinks.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "Hyperlinks.h"
19#include "advstd.h"
20#include "Common.h"
21#include "DBCStores.h"
22#include "Errors.h"
23#include "ObjectMgr.h"
24#include "SharedDefines.h"
25#include "SpellInfo.h"
26#include "SpellMgr.h"
27#include "QuestDef.h"
28#include "World.h"
29
30using namespace Trinity::Hyperlinks;
31
32inline uint8 toHex(char c) { return (c >= '0' && c <= '9') ? c - '0' + 0x10 : (c >= 'a' && c <= 'f') ? c - 'a' + 0x1a : 0x00; }
33// Validates a single hyperlink
35{
36 uint32 color = 0;
37 std::string_view tag;
38 std::string_view data;
39 std::string_view text;
40
41 //color tag
42 if (str.substr(0, 2) != "|c")
43 return {};
44 str.remove_prefix(2);
45
46 if (str.length() < 8)
47 return {};
48
49 for (uint8 i = 0; i < 8; ++i)
50 {
51 if (uint8 hex = toHex(str[i]))
52 color = (color << 4) | (hex & 0xf);
53 else
54 return {};
55 }
56 str.remove_prefix(8);
57
58 if (str.substr(0, 2) != "|H")
59 return {};
60 str.remove_prefix(2);
61
62 // tag+data part follows
63 if (size_t delimPos = str.find('|'); delimPos != std::string_view::npos)
64 {
65 tag = str.substr(0, delimPos);
66 str.remove_prefix(delimPos+1);
67 }
68 else
69 return {};
70
71 // split tag if : is present (data separator)
72 if (size_t dataStart = tag.find(':'); dataStart != std::string_view::npos)
73 {
74 data = tag.substr(dataStart+1);
75 tag = tag.substr(0, dataStart);
76 }
77
78 // ok, next should be link data end tag...
79 if (str.substr(0, 1) != "h")
80 return {};
81 str.remove_prefix(1);
82 // skip to final |
83 if (size_t end = str.find('|'); end != std::string_view::npos)
84 {
85 // check end tag
86 if (str.substr(end, 4) != "|h|r")
87 return {};
88 // check text brackets
89 if ((str[0] != '[') || (str[end - 1] != ']'))
90 return {};
91 text = str.substr(1, end - 2);
92 // tail
93 str = str.substr(end + 4);
94 }
95 else
96 return {};
97
98 // ok, valid hyperlink, return info
99 return { str, color, tag, data, text };
100}
101
102template <typename T>
104{
105 static bool IsTextValid(typename T::value_type, std::string_view) { return true; }
106 static bool IsColorValid(typename T::value_type, HyperlinkColor) { return true; }
107};
108
109template <>
110struct LinkValidator<LinkTags::achievement>
111{
112 static bool IsTextValid(AchievementLinkData const& data, std::string_view text)
113 {
114 if (text.empty())
115 return false;
116 for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
117 if (text == data.Achievement->Title[i])
118 return true;
119 return false;
120 }
121
123 {
124 return c == CHAT_LINK_COLOR_ACHIEVEMENT;
125 }
126};
127
128template <>
129struct LinkValidator<LinkTags::item>
130{
131 static bool IsTextValid(ItemLinkData const& data, std::string_view text)
132 {
133 std::array<char const*, 16> const* randomSuffixes = nullptr;
134 if (data.RandomProperty)
135 randomSuffixes = &data.RandomProperty->Name;
136 else if (data.RandomSuffix)
137 randomSuffixes = &data.RandomSuffix->Name;
138
139 if (data.IsBuggedInspectLink) /* DBC lookup will have failed on the client, so the link should've arrived without suffix */
140 randomSuffixes = nullptr;
141
142 for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
143 {
144 std::string_view name = data.Item->GetName(LocaleConstant(i));
145 if (name.empty())
146 continue;
147 if (randomSuffixes)
148 {
149 std::string_view randomSuffix((*randomSuffixes)[i]);
150 if (
151 (!randomSuffix.empty()) &&
152 (text.length() == (name.length() + 1 + randomSuffix.length())) &&
153 (text.substr(0, name.length()) == name) &&
154 (text[name.length()] == ' ') &&
155 (text.substr(name.length() + 1) == randomSuffix)
156 )
157 return true;
158 }
159 else if (text == name)
160 return true;
161 }
162 return false;
163 }
164
165 static bool IsColorValid(ItemLinkData const& data, HyperlinkColor c)
166 {
167 return c == ItemQualityColors[data.Item->GetQuality()];
168 }
169};
170
171template <>
172struct LinkValidator<LinkTags::quest>
173{
174 static bool IsTextValid(QuestLinkData const& data, std::string_view text)
175 {
176 if (text.empty())
177 return false;
178
179 if (text == data.Quest->GetLogTitle())
180 return true;
181
182 QuestLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId());
183 if (!locale)
184 return false;
185
186 for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
187 {
188 if (i == DEFAULT_LOCALE)
189 continue;
190
191 std::string_view name = ObjectMgr::GetLocaleString(locale->LogTitle, LocaleConstant(i));
192 if (!name.empty() && (text == name))
193 return true;
194 }
195
196 return false;
197 }
198
200 {
201 for (uint8 i = 0; i < MAX_QUEST_DIFFICULTY; ++i)
202 if (c == QuestDifficultyColors[i])
203 return true;
204 return false;
205 }
206};
207
208template <>
209struct LinkValidator<LinkTags::spell>
210{
211 static bool IsTextValid(SpellInfo const* info, std::string_view text)
212 {
213 for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
214 if (text == info->SpellName[i])
215 return true;
216 return false;
217 }
218
219 static bool IsColorValid(SpellInfo const*, HyperlinkColor c)
220 {
221 return c == CHAT_LINK_COLOR_SPELL;
222 }
223};
224
225template <>
226struct LinkValidator<LinkTags::enchant>
227{
228 static bool IsTextValid(SpellInfo const* info, std::string_view text)
229 {
231 return true;
232 SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(info->Id);
233 if (bounds.first == bounds.second)
234 return false;
235
236 for (auto pair = bounds.first; pair != bounds.second; ++pair)
237 {
238 SkillLineEntry const* skill = sSkillLineStore.LookupEntry(pair->second->SkillLine);
239 if (!skill)
240 return false;
241
242 for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
243 {
244 std::string_view skillName = skill->DisplayName[i];
245 std::string_view spellName = info->SpellName[i];
246 // alternate form [Skill Name: Spell Name]
247 if ((text.length() == (skillName.length() + 2 + spellName.length())) &&
248 (text.substr(0, skillName.length()) == skillName) &&
249 (text.substr(skillName.length(), 2) == ": ") &&
250 (text.substr(skillName.length() + 2) == spellName))
251 return true;
252 }
253 }
254 return false;
255 }
256
257 static bool IsColorValid(SpellInfo const*, HyperlinkColor c)
258 {
259 return c == CHAT_LINK_COLOR_ENCHANT;
260 }
261};
262
263template <>
264struct LinkValidator<LinkTags::glyph>
265{
266 static bool IsTextValid(GlyphLinkData const& data, std::string_view text)
267 {
268 if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.Glyph->SpellID))
270 return false;
271 }
272
274 {
275 return c == CHAT_LINK_COLOR_GLYPH;
276 }
277};
278
279template <>
280struct LinkValidator<LinkTags::talent>
281{
282 static bool IsTextValid(TalentLinkData const& data, std::string_view text)
283 {
284 SpellInfo const* info = data.Spell;
285 if (!info)
286 info = sSpellMgr->GetSpellInfo(data.Talent->SpellRank[0]);
287 if (!info)
288 return false;
290 }
291
293 {
294 return c == CHAT_LINK_COLOR_TALENT;
295 }
296};
297
298template <>
299struct LinkValidator<LinkTags::trade>
300{
301 static bool IsTextValid(TradeskillLinkData const& data, std::string_view text)
302 {
304 }
305
307 {
308 return c == CHAT_LINK_COLOR_TRADE;
309 }
310};
311
312template <typename TAG>
313static bool ValidateAs(HyperlinkInfo const& info)
314{
315 std::decay_t<typename TAG::value_type> t;
316 if (!TAG::StoreTo(t, info.data))
317 return false;
318
319 int32 const severity = static_cast<int32>(sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY));
320 if (severity >= 0)
321 {
323 return false;
324 if (severity >= 1)
325 {
327 return false;
328 }
329 }
330 return true;
331}
332
333#define TryValidateAs(T) do { if (info.tag == T::tag()) return ValidateAs<T>(info); } while (0);
334
335static bool ValidateLinkInfo(HyperlinkInfo const& info)
336{
337 using namespace LinkTags;
338 TryValidateAs(achievement);
339 TryValidateAs(area);
340 TryValidateAs(areatrigger);
341 TryValidateAs(creature);
342 TryValidateAs(creature_entry);
343 TryValidateAs(enchant);
344 TryValidateAs(gameevent);
345 TryValidateAs(gameobject);
346 TryValidateAs(gameobject_entry);
347 TryValidateAs(glyph);
348 TryValidateAs(item);
349 TryValidateAs(itemset);
350 TryValidateAs(player);
351 TryValidateAs(quest);
352 TryValidateAs(skill);
353 TryValidateAs(spell);
354 TryValidateAs(talent);
355 TryValidateAs(taxinode);
356 TryValidateAs(tele);
357 TryValidateAs(title);
358 TryValidateAs(trade);
359 return false;
360}
361
362// Validates all hyperlinks and control sequences contained in str
363bool Trinity::Hyperlinks::CheckAllLinks(std::string_view str)
364{
365 // Step 1: Disallow all control sequences except ||, |H, |h, |c and |r
366 {
367 std::string_view::size_type pos = 0;
368 while ((pos = str.find('|', pos)) != std::string::npos)
369 {
370 ++pos;
371 if (pos == str.length())
372 return false;
373 char next = str[pos];
374 if (next == 'H' || next == 'h' || next == 'c' || next == 'r' || next == '|')
375 ++pos;
376 else
377 return false;
378 }
379 }
380
381 // Step 2: Parse all link sequences
382 // They look like this: |c<color>|H<linktag>:<linkdata>|h[<linktext>]|h|r
383 // - <color> is 8 hex characters AARRGGBB
384 // - <linktag> is arbitrary length [a-z_]
385 // - <linkdata> is arbitrary length, no | contained
386 // - <linktext> is printable
387 {
388 std::string::size_type pos;
389 while ((pos = str.find('|')) != std::string::npos)
390 {
391 if (str[pos + 1] == '|') // this is an escaped pipe character (||)
392 {
393 str = str.substr(pos + 2);
394 continue;
395 }
396
397 HyperlinkInfo info = ParseSingleHyperlink(str.substr(pos));
398 if (!info || !ValidateLinkInfo(info))
399 return false;
400
401 // tag is fine, find the next one
402 str = info.tail;
403 }
404 }
405
406 // all tags are valid
407 return true;
408}
LocaleConstant
Definition Common.h:48
@ TOTAL_LOCALES
Definition Common.h:59
#define DEFAULT_LOCALE
Definition Common.h:62
DBCStorage< SkillLineEntry > sSkillLineStore(SkillLinefmt)
uint8_t uint8
Definition Define.h:135
int32_t int32
Definition Define.h:129
uint32_t uint32
Definition Define.h:133
#define sObjectMgr
Definition ObjectMgr.h:1721
uint32 constexpr ItemQualityColors[MAX_ITEM_QUALITY]
@ CHAT_LINK_COLOR_TRADE
@ CHAT_LINK_COLOR_SPELL
@ CHAT_LINK_COLOR_ACHIEVEMENT
@ CHAT_LINK_COLOR_TALENT
@ CHAT_LINK_COLOR_GLYPH
@ CHAT_LINK_COLOR_ENCHANT
uint32 constexpr QuestDifficultyColors[MAX_QUEST_DIFFICULTY]
size_t constexpr MAX_QUEST_DIFFICULTY
#define sSpellMgr
Definition SpellMgr.h:738
std::pair< SkillLineAbilityMap::const_iterator, SkillLineAbilityMap::const_iterator > SkillLineAbilityMapBounds
Definition SpellMgr.h:537
static std::string_view GetLocaleString(std::vector< std::string > const &data, LocaleConstant locale)
Definition ObjectMgr.h:1519
std::string const & GetLogTitle() const
Definition QuestDef.h:261
uint32 GetQuestId() const
Definition QuestDef.h:228
uint32 Id
Definition SpellInfo.h:289
std::array< char const *, 16 > SpellName
Definition SpellInfo.h:352
#define sWorld
Definition World.h:901
@ CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY
Definition World.h:294
std::array< char const *, 16 > Title
std::array< char const *, 16 > Name
std::array< char const *, 16 > Name
uint32 GetQuality() const
std::string const & GetName(LocaleConstant locale) const
static bool IsColorValid(AchievementLinkData const &, HyperlinkColor c)
static bool IsTextValid(AchievementLinkData const &data, std::string_view text)
static bool IsTextValid(SpellInfo const *info, std::string_view text)
static bool IsColorValid(SpellInfo const *, HyperlinkColor c)
static bool IsTextValid(GlyphLinkData const &data, std::string_view text)
static bool IsColorValid(GlyphLinkData const &, HyperlinkColor c)
static bool IsColorValid(ItemLinkData const &data, HyperlinkColor c)
static bool IsTextValid(ItemLinkData const &data, std::string_view text)
static bool IsColorValid(QuestLinkData const &, HyperlinkColor c)
static bool IsTextValid(QuestLinkData const &data, std::string_view text)
static bool IsTextValid(SpellInfo const *info, std::string_view text)
static bool IsColorValid(SpellInfo const *, HyperlinkColor c)
static bool IsTextValid(TalentLinkData const &data, std::string_view text)
static bool IsColorValid(TalentLinkData const &, HyperlinkColor c)
static bool IsColorValid(TradeskillLinkData const &, HyperlinkColor c)
static bool IsTextValid(TradeskillLinkData const &data, std::string_view text)
static bool IsTextValid(typename T::value_type, std::string_view)
static bool IsColorValid(typename T::value_type, HyperlinkColor)
std::vector< std::string > LogTitle
Definition QuestDef.h:180
char const * DisplayName[16]
std::array< uint32, MAX_TALENT_RANK > SpellRank