/*
* 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;
}