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 ItemLocale const* locale = sObjectMgr->GetItemLocale(data.Item->ItemId);
134
135 std::array<char const*, 16> const* randomSuffixes = nullptr;
136 if (data.RandomProperty)
137 randomSuffixes = &data.RandomProperty->Name;
138 else if (data.RandomSuffix)
139 randomSuffixes = &data.RandomSuffix->Name;
140
141 if (data.IsBuggedInspectLink) /* DBC lookup will have failed on the client, so the link should've arrived without suffix */
142 randomSuffixes = nullptr;
143
144 for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
145 {
146 if (!locale && i != DEFAULT_LOCALE)
147 continue;
148 std::string_view name = (i == DEFAULT_LOCALE) ? data.Item->Name1 : ObjectMgr::GetLocaleString(locale->Name, i);
149 if (name.empty())
150 continue;
151 if (randomSuffixes)
152 {
153 std::string_view randomSuffix((*randomSuffixes)[i]);
154 if (
155 (!randomSuffix.empty()) &&
156 (text.length() == (name.length() + 1 + randomSuffix.length())) &&
157 (text.substr(0, name.length()) == name) &&
158 (text[name.length()] == ' ') &&
159 (text.substr(name.length() + 1) == randomSuffix)
160 )
161 return true;
162 }
163 else if (text == name)
164 return true;
165 }
166 return false;
167 }
168
169 static bool IsColorValid(ItemLinkData const& data, HyperlinkColor c)
170 {
171 return c == ItemQualityColors[data.Item->Quality];
172 }
173};
174
175template <>
176struct LinkValidator<LinkTags::quest>
177{
178 static bool IsTextValid(QuestLinkData const& data, std::string_view text)
179 {
180 if (text.empty())
181 return false;
182
183 if (text == data.Quest->GetTitle())
184 return true;
185
186 QuestLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId());
187 if (!locale)
188 return false;
189
190 for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
191 {
192 if (i == DEFAULT_LOCALE)
193 continue;
194
195 std::string_view name = ObjectMgr::GetLocaleString(locale->Title, i);
196 if (!name.empty() && (text == name))
197 return true;
198 }
199
200 return false;
201 }
202
204 {
205 for (uint8 i = 0; i < MAX_QUEST_DIFFICULTY; ++i)
206 if (c == QuestDifficultyColors[i])
207 return true;
208 return false;
209 }
210};
211
212template <>
213struct LinkValidator<LinkTags::spell>
214{
215 static bool IsTextValid(SpellInfo const* info, std::string_view text)
216 {
217 for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
218 if (text == info->SpellName[i])
219 return true;
220 return false;
221 }
222
223 static bool IsColorValid(SpellInfo const*, HyperlinkColor c)
224 {
225 return c == CHAT_LINK_COLOR_SPELL;
226 }
227};
228
229template <>
230struct LinkValidator<LinkTags::enchant>
231{
232 static bool IsTextValid(SpellInfo const* info, std::string_view text)
233 {
235 return true;
236 SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(info->Id);
237 if (bounds.first == bounds.second)
238 return false;
239
240 for (auto pair = bounds.first; pair != bounds.second; ++pair)
241 {
242 SkillLineEntry const* skill = sSkillLineStore.LookupEntry(pair->second->SkillLine);
243 if (!skill)
244 return false;
245
246 for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
247 {
248 std::string_view skillName = skill->DisplayName[i];
249 std::string_view spellName = info->SpellName[i];
250 // alternate form [Skill Name: Spell Name]
251 if ((text.length() == (skillName.length() + 2 + spellName.length())) &&
252 (text.substr(0, skillName.length()) == skillName) &&
253 (text.substr(skillName.length(), 2) == ": ") &&
254 (text.substr(skillName.length() + 2) == spellName))
255 return true;
256 }
257 }
258 return false;
259 }
260
261 static bool IsColorValid(SpellInfo const*, HyperlinkColor c)
262 {
263 return c == CHAT_LINK_COLOR_ENCHANT;
264 }
265};
266
267template <>
268struct LinkValidator<LinkTags::glyph>
269{
270 static bool IsTextValid(GlyphLinkData const& data, std::string_view text)
271 {
272 if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.Glyph->SpellID))
274 return false;
275 }
276
278 {
279 return c == CHAT_LINK_COLOR_GLYPH;
280 }
281};
282
283template <>
284struct LinkValidator<LinkTags::talent>
285{
286 static bool IsTextValid(TalentLinkData const& data, std::string_view text)
287 {
288 SpellInfo const* info = data.Spell;
289 if (!info)
290 info = sSpellMgr->GetSpellInfo(data.Talent->SpellRank[0]);
291 if (!info)
292 return false;
294 }
295
297 {
298 return c == CHAT_LINK_COLOR_TALENT;
299 }
300};
301
302template <>
303struct LinkValidator<LinkTags::trade>
304{
305 static bool IsTextValid(TradeskillLinkData const& data, std::string_view text)
306 {
308 }
309
311 {
312 return c == CHAT_LINK_COLOR_TRADE;
313 }
314};
315
316template <typename TAG>
317static bool ValidateAs(HyperlinkInfo const& info)
318{
319 std::decay_t<typename TAG::value_type> t;
320 if (!TAG::StoreTo(t, info.data))
321 return false;
322
323 int32 const severity = static_cast<int32>(sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY));
324 if (severity >= 0)
325 {
327 return false;
328 if (severity >= 1)
329 {
331 return false;
332 }
333 }
334 return true;
335}
336
337#define TryValidateAs(T) do { if (info.tag == T::tag()) return ValidateAs<T>(info); } while (0);
338
339static bool ValidateLinkInfo(HyperlinkInfo const& info)
340{
341 using namespace LinkTags;
342 TryValidateAs(achievement);
343 TryValidateAs(area);
344 TryValidateAs(areatrigger);
345 TryValidateAs(creature);
346 TryValidateAs(creature_entry);
347 TryValidateAs(enchant);
348 TryValidateAs(gameevent);
349 TryValidateAs(gameobject);
350 TryValidateAs(gameobject_entry);
351 TryValidateAs(glyph);
352 TryValidateAs(item);
353 TryValidateAs(itemset);
354 TryValidateAs(player);
355 TryValidateAs(quest);
356 TryValidateAs(skill);
357 TryValidateAs(spell);
358 TryValidateAs(talent);
359 TryValidateAs(taxinode);
360 TryValidateAs(tele);
361 TryValidateAs(title);
362 TryValidateAs(trade);
363 return false;
364}
365
366// Validates all hyperlinks and control sequences contained in str
367bool Trinity::Hyperlinks::CheckAllLinks(std::string_view str)
368{
369 // Step 1: Disallow all control sequences except ||, |H, |h, |c and |r
370 {
371 std::string_view::size_type pos = 0;
372 while ((pos = str.find('|', pos)) != std::string::npos)
373 {
374 ++pos;
375 if (pos == str.length())
376 return false;
377 char next = str[pos];
378 if (next == 'H' || next == 'h' || next == 'c' || next == 'r' || next == '|')
379 ++pos;
380 else
381 return false;
382 }
383 }
384
385 // Step 2: Parse all link sequences
386 // They look like this: |c<color>|H<linktag>:<linkdata>|h[<linktext>]|h|r
387 // - <color> is 8 hex characters AARRGGBB
388 // - <linktag> is arbitrary length [a-z_]
389 // - <linkdata> is arbitrary length, no | contained
390 // - <linktext> is printable
391 {
392 std::string::size_type pos;
393 while ((pos = str.find('|')) != std::string::npos)
394 {
395 if (str[pos + 1] == '|') // this is an escaped pipe character (||)
396 {
397 str = str.substr(pos + 2);
398 continue;
399 }
400
401 HyperlinkInfo info = ParseSingleHyperlink(str.substr(pos));
402 if (!info || !ValidateLinkInfo(info))
403 return false;
404
405 // tag is fine, find the next one
406 str = info.tail;
407 }
408 }
409
410 // all tags are valid
411 return true;
412}
@ 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, size_t locale)
Definition ObjectMgr.h:1525
uint32 GetQuestId() const
Definition QuestDef.h:229
std::string const & GetTitle() const
Definition QuestDef.h:263
uint32 Id
Definition SpellInfo.h:289
std::array< char const *, 16 > SpellName
Definition SpellInfo.h:352
#define sWorld
Definition World.h:900
@ CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY
Definition World.h:293
std::array< char const *, 16 > Title
std::vector< std::string > Name
std::array< char const *, 16 > Name
std::array< char const *, 16 > Name
std::string Name1
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 > Title
Definition QuestDef.h:183
char const * DisplayName[16]
std::array< uint32, MAX_TALENT_RANK > SpellRank