/* * Copyright (C) 2008-2018 TrinityCore * * 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 . */ #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); else return nullptr; } // 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 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 { 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 { 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 { 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 { 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 { 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::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 { static bool IsTextValid(TalentEntry const* talent, char const* pos, size_t len) { if (SpellInfo const* info = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE)) return LinkValidator::IsTextValid(info, pos, len); return false; } static bool IsColorValid(TalentEntry const*, HyperlinkColor c) { return c == CHAT_LINK_COLOR_TALENT; } }; template <> struct LinkValidator { static bool IsTextValid(TradeskillLinkData const& data, char const* pos, size_t len) { return LinkValidator::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 t; \ if (!LinkTags::tagname::StoreTo(t, info.data.first, info.data.second)) \ return false; \ if (!LinkValidator::IsColorValid(t, info.color)) \ return false; \ if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY)) \ if (!LinkValidator::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|H:|h[]|h|r // - is 8 hex characters AARRGGBB // - is arbitrary length [a-z_] // - is arbitrary length, no | contained // - 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; }