diff options
Diffstat (limited to 'src/server/game/Chat/Hyperlinks.cpp')
-rw-r--r-- | src/server/game/Chat/Hyperlinks.cpp | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/src/server/game/Chat/Hyperlinks.cpp b/src/server/game/Chat/Hyperlinks.cpp new file mode 100644 index 00000000000..fbc4a0c3995 --- /dev/null +++ b/src/server/game/Chat/Hyperlinks.cpp @@ -0,0 +1,408 @@ +/* + * 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 "advstd.h" +#include "Common.h" +#include "DBCStores.h" +#include "Errors.h" +#include "SharedDefines.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include "QuestDef.h" +#include "World.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 (uint8 i = 0; i < TOTAL_LOCALES; ++i) + 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) + { + ItemLocale const* locale = sObjectMgr->GetItemLocale(data.Item->ItemId); + + char const* const* randomSuffix = nullptr; + if (data.RandomPropertyId < 0) + { + if (ItemRandomSuffixEntry const* suffixEntry = sItemRandomSuffixStore.LookupEntry(-data.RandomPropertyId)) + randomSuffix = suffixEntry->nameSuffix; + else + return false; + } + else if (data.RandomPropertyId > 0) + { + if (ItemRandomPropertiesEntry const* propEntry = sItemRandomPropertiesStore.LookupEntry(data.RandomPropertyId)) + randomSuffix = propEntry->nameSuffix; + else + return false; + } + + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + { + if (!locale && i != DEFAULT_LOCALE) + continue; + std::string const& name = (i == DEFAULT_LOCALE) ? data.Item->Name1 : locale->Name[i]; + if (name.empty()) + continue; + if (randomSuffix) + { + if (len > name.length() + 1 && + (strncmp(name.c_str(), pos, name.length()) == 0) && + (*(pos + name.length()) == ' ') && + equal_with_len(randomSuffix[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.Item->Quality]; + } +}; + +template <> +struct LinkValidator<LinkTags::quest> +{ + static bool IsTextValid(QuestLinkData const& data, char const* pos, size_t len) + { + QuestLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId()); + if (!locale) + return equal_with_len(data.Quest->GetTitle().c_str(), pos, len); + + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + { + std::string const& name = (i == DEFAULT_LOCALE) ? data.Quest->GetTitle() : locale->Title[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(SpellInfo const* info, char const* pos, size_t len) + { + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + if (equal_with_len(info->SpellName[i], pos, len)) + return true; + return false; + } + + static bool IsColorValid(SpellInfo 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->skillId); + if (!skill) + return false; + + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + { + char const* skillName = skill->name[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::glyph> +{ + static bool IsTextValid(GlyphLinkData const& data, char const* pos, size_t len) + { + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.Glyph->SpellId)) + return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + return false; + } + + static bool IsColorValid(GlyphLinkData const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_GLYPH; + } +}; + +template <> +struct LinkValidator<LinkTags::talent> +{ + static bool IsTextValid(TalentLinkData const& data, char const* pos, size_t len) + { + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.Talent->RankID[data.Rank-1])) + return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + return false; + } + + static bool IsColorValid(TalentLinkData 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) \ +{ \ + using taginfo = typename LinkTags::tagname; \ + ASSERT(!strcmp(taginfo::tag(), #tagname)); \ + if (info.tag.second == strlen(taginfo::tag()) && \ + !strncmp(info.tag.first, taginfo::tag(), strlen(taginfo::tag()))) \ + { \ + advstd::remove_cvref_t<typename taginfo::value_type> t; \ + if (!taginfo::StoreTo(t, info.data.first, info.data.second)) \ + return false; \ + if (!LinkValidator<taginfo>::IsColorValid(t, info.color)) \ + return false; \ + if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY)) \ + if (!LinkValidator<taginfo>::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(glyph); + 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; +} |