aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/modules/m_codepage.cpp216
1 files changed, 155 insertions, 61 deletions
diff --git a/src/modules/m_codepage.cpp b/src/modules/m_codepage.cpp
index d59078e44..1e19fcf15 100644
--- a/src/modules/m_codepage.cpp
+++ b/src/modules/m_codepage.cpp
@@ -19,20 +19,101 @@
#include "inspircd.h"
-typedef std::bitset<UCHAR_MAX + 1> AllowedChars;
+class Codepage
+{
+ public:
+ enum AllowCharacterResult
+ {
+ // The character is allowed in a nick.
+ ACR_OKAY,
+
+ // The character is never valid in a nick.
+ ACR_NOT_VALID,
+
+ // The character is not valid at the front of a nick.
+ ACR_NOT_VALID_AT_FRONT
+ };
+
+ // The mapping of lower case characters to upper case characters.
+ unsigned char casemap[UCHAR_MAX + 1];
+
+ // Initialises the Codepage class.
+ Codepage()
+ {
+ for (size_t i = 0; i <= UCHAR_MAX; ++i)
+ casemap[i] = i;
+ }
+
+ // Destroys the Codepage class.
+ virtual ~Codepage() = default;
-namespace
+ // Specifies that a character is allowed.
+ virtual AllowCharacterResult AllowCharacter(uint32_t character, bool front)
+ {
+ if (front)
+ {
+ // Nicknames can not begin with a number as that would collide with
+ // a user identifier.
+ if (character >= '0' && character <= '9')
+ return ACR_NOT_VALID_AT_FRONT;
+
+ // Nicknames can not begin with a : as it has special meaning within
+ // the IRC message format.
+ if (character == ':')
+ return ACR_NOT_VALID_AT_FRONT;
+ }
+
+ // Nicknames can never contain NUL, CR, LF, or SPACE as they are either
+ // banned within an IRC message or have special meaning within the IRC
+ // message format.
+ if (!character || character == '\n' || character == '\r' || character == ' ')
+ return ACR_NOT_VALID;
+
+ // The character is probably okay?
+ return ACR_OKAY;
+ }
+
+ // Determines whether a nickname is valid.
+ virtual bool IsValidNick(const std::string& nick) = 0;
+
+ // Retrieves the link data for this codepage.
+ virtual std::string LinkData() const = 0;
+
+ // Maps an upper case character to a lower case character.
+ virtual bool Map(unsigned long upper, unsigned long lower) = 0;
+};
+
+class SingleByteCodepage final
+ : public Codepage
{
+ private:
+ typedef std::bitset<UCHAR_MAX + 1> AllowedChars;
+
// The characters which are allowed in nicknames.
AllowedChars allowedchars;
// The characters which are allowed at the front of a nickname.
AllowedChars allowedfrontchars;
- // The mapping of lower case characters to upper case characters.
- unsigned char casemap[UCHAR_MAX];
+ public:
+ AllowCharacterResult AllowCharacter(uint32_t character, bool front) override
+ {
+ // Single byte codepage can, as their name suggests, only be one byte in size.
+ if (character > UCHAR_MAX)
+ return ACR_NOT_VALID;
- bool IsValidNick(const std::string& nick)
+ // Check the common allowed character rules.
+ AllowCharacterResult result = Codepage::AllowCharacter(character, front);
+ if (result != ACR_OKAY)
+ return result;
+
+ // The character is okay.
+ allowedchars.set(character);
+ allowedfrontchars.set(character, front);
+ return ACR_OKAY;
+ }
+
+ bool IsValidNick(const std::string& nick) override
{
if (nick.empty() || nick.length() > ServerInstance->Config->Limits.MaxNick)
return false;
@@ -52,12 +133,46 @@ namespace
return true;
}
-}
-class ModuleCodepage
+ std::string LinkData() const override
+ {
+ std::stringstream linkdata;
+
+ linkdata << "front=";
+ for (size_t i = 0; i < allowedfrontchars.size(); ++i)
+ if (allowedfrontchars[i])
+ linkdata << static_cast<unsigned char>(i);
+
+ linkdata << "&middle=";
+ for (size_t i = 0; i < allowedchars.size(); ++i)
+ if (allowedchars[i])
+ linkdata << static_cast<unsigned char>(i);
+
+ linkdata << "&map=";
+ for (size_t i = 0; i < sizeof(casemap); ++i)
+ if (casemap[i] != i)
+ linkdata << static_cast<unsigned char>(i) << casemap[i] << ',';
+
+ return linkdata.str();
+ }
+
+ bool Map(unsigned long upper, unsigned long lower) override
+ {
+ if (upper > UCHAR_MAX || lower > UCHAR_MAX)
+ return false;
+
+ casemap[upper] = lower;
+ return true;
+ }
+};
+
+class ModuleCodepage final
: public Module
{
private:
+ // The currently active codepage.
+ std::unique_ptr<Codepage> codepage;
+
// The character map which was set before this module was loaded.
const unsigned char* origcasemap;
@@ -114,6 +229,7 @@ class ModuleCodepage
public:
ModuleCodepage()
: Module(VF_VENDOR | VF_COMMON, "Allows the server administrator to define what characters are allowed in nicknames and how characters should be compared in a case insensitive way.")
+ , codepage(nullptr)
, origcasemap(national_case_insensitive_map)
, origcasemapname(ServerInstance->Config->CaseMapping)
, origisnick(ServerInstance->IsNick)
@@ -128,7 +244,8 @@ class ModuleCodepage
ServerInstance->Config->CaseMapping = origcasemapname;
national_case_insensitive_map = origcasemap;
CheckDuplicateNick();
- CheckRehash(casemap);
+ if (codepage) // nullptr if ReadConfig throws on load.
+ CheckRehash(codepage->casemap);
}
void ReadConfig(ConfigStatus& status) override
@@ -137,92 +254,69 @@ class ModuleCodepage
if (name.empty())
throw ModuleException("<codepage:name> is a required field!");
- AllowedChars newallowedchars;
- AllowedChars newallowedfrontchars;
-
+ std::unique_ptr<Codepage> newcodepage = std::make_unique<SingleByteCodepage>();
for (const auto& [_, tag] : ServerInstance->Config->ConfTags("cpchars"))
{
- unsigned char begin = tag->getUInt("begin", tag->getUInt("index", 0), 1, UCHAR_MAX);
+ unsigned long begin = tag->getUInt("begin", tag->getUInt("index", 0));
if (!begin)
throw ModuleException("<cpchars> tag without index or begin specified at " + tag->source.str());
- unsigned char end = tag->getUInt("end", begin, 1, UCHAR_MAX);
+ unsigned long end = tag->getUInt("end", begin);
if (begin > end)
throw ModuleException("<cpchars:begin> must be lower than <cpchars:end> at " + tag->source.str());
bool front = tag->getBool("front", false);
- for (unsigned short pos = begin; pos <= end; ++pos)
+ for (unsigned long pos = begin; pos <= end; ++pos)
{
- if (pos == '\n' || pos == '\r' || pos == ' ')
+ switch (newcodepage->AllowCharacter(pos, front))
{
- throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden character: %u at %s",
- pos, tag->source.str().c_str()));
- }
+ case Codepage::ACR_OKAY:
+ ServerInstance->Logs.Log(MODNAME, LOG_DEBUG, "Marked %lu (%.4s) as allowed (front: %s)",
+ pos, reinterpret_cast<unsigned char*>(&pos), front ? "yes" : "no");
+ break;
- if (front && (pos == ':' || isdigit(pos)))
- {
- throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden front character: %u at %s",
- pos, tag->source.str().c_str()));
- }
+ case Codepage::ACR_NOT_VALID:
+ throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden character: %lu at %s",
+ pos, tag->source.str().c_str()));
- newallowedchars.set(pos);
- newallowedfrontchars.set(pos, front);
- ServerInstance->Logs.Log(MODNAME, LOG_DEBUG, "Marked %u (%c) as allowed (front: %s)",
- pos, pos, front ? "yes" : "no");
+ case Codepage::ACR_NOT_VALID_AT_FRONT:
+ throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden front character: %lu at %s",
+ pos, tag->source.str().c_str()));
+ }
}
}
- unsigned char newcasemap[UCHAR_MAX];
- for (size_t i = 0; i < UCHAR_MAX; ++i)
- newcasemap[i] = i;
-
for (const auto& [_, tag] : ServerInstance->Config->ConfTags("cpcase"))
{
- unsigned char lower = tag->getUInt("lower", 0, 1, UCHAR_MAX);
+ unsigned long lower = tag->getUInt("lower", 0);
if (!lower)
throw ModuleException("<cpcase:lower> is required at " + tag->source.str());
- unsigned char upper = tag->getUInt("upper", 0, 1, UCHAR_MAX);
+ unsigned long upper = tag->getUInt("upper", 0);
if (!upper)
throw ModuleException("<cpcase:upper> is required at " + tag->source.str());
- newcasemap[upper] = lower;
- ServerInstance->Logs.Log(MODNAME, LOG_DEBUG, "Marked %u (%c) as the lower case version of %u (%c)",
- lower, lower, upper, upper);
- }
+ if (!newcodepage->Map(upper, lower))
+ throw ModuleException("Malformed <cpcase> tag at " + tag->source.str());
- std::swap(allowedchars, newallowedchars);
- std::swap(allowedfrontchars, newallowedfrontchars);
- std::swap(casemap, newcasemap);
+ ServerInstance->Logs.Log(MODNAME, LOG_DEBUG, "Marked %lu (%.4s) as the lower case version of %lu (%.4s)",
+ lower, reinterpret_cast<unsigned char*>(&lower), upper, reinterpret_cast<unsigned char*>(&upper));
+ }
- ServerInstance->IsNick = &IsValidNick;
+ std::swap(codepage, newcodepage);
+ ServerInstance->IsNick = [this](const std::string& nick) { return codepage->IsValidNick(nick); };
CheckInvalidNick();
ServerInstance->Config->CaseMapping = name;
- national_case_insensitive_map = casemap;
- CheckRehash(newcasemap);
+ national_case_insensitive_map = codepage->casemap;
+ if (newcodepage) // nullptr on first read.
+ CheckRehash(newcodepage->casemap);
}
void GetLinkData(std::string& data) override
{
- std::stringstream linkdata;
-
- linkdata << "front=";
- for (size_t i = 0; i < allowedfrontchars.size(); ++i)
- if (allowedfrontchars[i])
- linkdata << static_cast<unsigned char>(i);
-
- linkdata << "&middle=";
- for (size_t i = 0; i < allowedchars.size(); ++i)
- if (allowedchars[i])
- linkdata << static_cast<unsigned char>(i);
-
- linkdata << "&map=";
- for (size_t i = 0; i < sizeof(casemap); ++i)
- if (casemap[i] != i)
- linkdata << static_cast<unsigned char>(i) << casemap[i] << ',';
-
- data = linkdata.str();
+ data.assign(codepage ? codepage->LinkData() : "");
}
};
+
MODULE_INIT(ModuleCodepage)