diff options
author | Sadie Powell <sadie@witchery.services> | 2024-06-24 14:29:55 +0100 |
---|---|---|
committer | Sadie Powell <sadie@witchery.services> | 2024-06-24 14:29:55 +0100 |
commit | 693eeed762eac490b1289f8f4428ff0b5bbf1672 (patch) | |
tree | 974cbad287da04fc9e137d7f51092efd2e474acd | |
parent | 6e5713d64a379fc64c7ff6658362d02b3618c3ca (diff) |
Rework how CTCP messages are sent and received.
-rw-r--r-- | include/anope.h | 14 | ||||
-rw-r--r-- | include/protocol.h | 4 | ||||
-rw-r--r-- | modules/botserv/bs_control.cpp | 6 | ||||
-rw-r--r-- | modules/botserv/bs_kick.cpp | 8 | ||||
-rw-r--r-- | modules/irc2sql/irc2sql.cpp | 41 | ||||
-rw-r--r-- | src/messages.cpp | 14 | ||||
-rw-r--r-- | src/misc.cpp | 47 | ||||
-rw-r--r-- | src/protocol.cpp | 27 |
8 files changed, 91 insertions, 70 deletions
diff --git a/include/anope.h b/include/anope.h index 8d58acdce..b66a211b3 100644 --- a/include/anope.h +++ b/include/anope.h @@ -601,6 +601,20 @@ namespace Anope /** Expands a module path. */ inline auto ExpandModule(const Anope::string &path) { return Expand(ModuleDir, path); } + /** Formats a CTCP message for sending to a client. + * @param name The name of the CTCP. + * @param body If present then the body of the CTCP. + * @return A formatted CTCP ready to send to a client. + */ + extern CoreExport Anope::string FormatCTCP(const Anope::string &name, const Anope::string &body = ""); + + /** Parses a CTCP message received from a client. + * @param text The raw message to parse. + * @param name The location to store the name of the CTCP. + * @param body The location to store body of the CTCP if one is present. + * @return True if the message was a well formed CTCP; otherwise, false. + */ + extern CoreExport bool ParseCTCP(const Anope::string &text, Anope::string &name, Anope::string &body); } /** sepstream allows for splitting token separated lists. diff --git a/include/protocol.h b/include/protocol.h index d69fa66fc..7f36a2917 100644 --- a/include/protocol.h +++ b/include/protocol.h @@ -43,7 +43,6 @@ public: virtual void SendNotice(const MessageSource &source, const Anope::string &dest, const Anope::string &msg, const Anope::map<Anope::string> &tags = {}); virtual void SendPrivmsg(const MessageSource &source, const Anope::string &dest, const Anope::string &msg, const Anope::map<Anope::string> &tags = {}); - virtual void SendCTCPInternal(const MessageSource &, const Anope::string &dest, const Anope::string &buf); /** Parses an incoming message from the IRC server. * @param message The message to parse. @@ -213,9 +212,6 @@ public: virtual void SendKick(const MessageSource &source, const Channel *chan, User *user, const Anope::string &msg); - virtual void SendAction(const MessageSource &source, const Anope::string &dest, const char *fmt, ...) ATTR_FORMAT(4, 5); - virtual void SendCTCP(const MessageSource &source, const Anope::string &dest, const char *fmt, ...) ATTR_FORMAT(4, 5); - virtual void SendGlobalNotice(BotInfo *bi, const Server *dest, const Anope::string &msg) = 0; virtual void SendGlobalPrivmsg(BotInfo *bi, const Server *desc, const Anope::string &msg) = 0; diff --git a/modules/botserv/bs_control.cpp b/modules/botserv/bs_control.cpp index 1784914b6..4847c8470 100644 --- a/modules/botserv/bs_control.cpp +++ b/modules/botserv/bs_control.cpp @@ -111,11 +111,7 @@ public: return; } - message = message.replace_all_cs("\1", ""); - if (message.empty()) - return; - - IRCD->SendAction(*ci->bi, ci->name, "%s", message.c_str()); + IRCD->SendPrivmsg(*ci->bi, ci->name, Anope::FormatCTCP("ACTION", message)); ci->bi->lastmsg = Anope::CurTime; bool override = !source.AccessFor(ci).HasPriv("SAY"); diff --git a/modules/botserv/bs_kick.cpp b/modules/botserv/bs_kick.cpp index 7d67a6577..f2a39dbf1 100644 --- a/modules/botserv/bs_kick.cpp +++ b/modules/botserv/bs_kick.cpp @@ -1215,11 +1215,9 @@ public: /* If it's a /me, cut the CTCP part because the ACTION will cause * problems with the caps or badwords kicker */ - if (realbuf.substr(0, 8).equals_ci("\1ACTION ") && realbuf[realbuf.length() - 1] == '\1') - { - realbuf.erase(0, 8); - realbuf.erase(realbuf.length() - 1); - } + Anope::string ctcpname, ctcpbody; + if (Anope::ParseCTCP(msg, ctcpname, ctcpbody) && ctcpname.equals_ci("ACTION")) + realbuf = ctcpbody; if (realbuf.empty()) return; diff --git a/modules/irc2sql/irc2sql.cpp b/modules/irc2sql/irc2sql.cpp index fdd80cdf9..917e37fd3 100644 --- a/modules/irc2sql/irc2sql.cpp +++ b/modules/irc2sql/irc2sql.cpp @@ -117,7 +117,7 @@ void IRC2SQL::OnUserConnect(User *u, bool &exempt) this->RunQuery(query); if (ctcpuser && (Me->IsSynced() || ctcpeob) && u->server != Me) - IRCD->SendPrivmsg(StatServ, u->GetUID(), "\1VERSION\1"); + IRCD->SendPrivmsg(StatServ, u->GetUID(), Anope::FormatCTCP("VERSION")); } @@ -299,28 +299,27 @@ void IRC2SQL::OnTopicUpdated(User *source, Channel *c, const Anope::string &user void IRC2SQL::OnBotNotice(User *u, BotInfo *bi, Anope::string &message, const Anope::map<Anope::string> &tags) { - Anope::string versionstr; if (bi != StatServ) return; - if (message[0] == '\1' && message[message.length() - 1] == '\1') - { - if (message.substr(0, 9).equals_ci("\1VERSION ")) - { - if (u->HasExt("CTCPVERSION")) - return; - u->Extend<bool>("CTCPVERSION"); - - versionstr = Anope::NormalizeBuffer(message.substr(9, message.length() - 10)); - if (versionstr.empty()) - return; - query = "UPDATE `" + prefix + "user` " - "SET version=@version@ " - "WHERE nick=@nick@"; - query.SetValue("version", versionstr); - query.SetValue("nick", u->nick); - this->RunQuery(query); - } - } + + Anope::string ctcpname, ctcpbody; + if (!Anope::ParseCTCP(message, ctcpname, ctcpbody) || ctcpname != "VERSION") + return; + + if (u->HasExt("CTCPVERSION")) + return; + + u->Extend<bool>("CTCPVERSION"); + auto versionstr = Anope::NormalizeBuffer(message.substr(9, message.length() - 10)); + if (versionstr.empty()) + return; + + query = "UPDATE `" + prefix + "user` " + "SET version=@version@ " + "WHERE nick=@nick@"; + query.SetValue("version", versionstr); + query.SetValue("nick", u->nick); + this->RunQuery(query); } MODULE_INIT(IRC2SQL) diff --git a/src/messages.cpp b/src/messages.cpp index 9a0a7a539..512da3e95 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -342,19 +342,17 @@ void Privmsg::Run(MessageSource &source, const std::vector<Anope::string> ¶m if (bi) { - if (message[0] == '\1' && message[message.length() - 1] == '\1') + Anope::string ctcpname, ctcpbody; + if (Anope::ParseCTCP(message, ctcpname, ctcpbody)) { - if (message.substr(0, 6).equals_ci("\1PING ")) + if (ctcpname.equals_ci("PING")) { - Anope::string buf = message; - buf.erase(buf.begin()); - buf.erase(buf.end() - 1); - IRCD->SendCTCP(bi, u->nick, "%s", buf.c_str()); + IRCD->SendNotice(bi, u->nick, Anope::FormatCTCP("PING", ctcpbody)); } - else if (message.substr(0, 9).equals_ci("\1VERSION\1")) + else if (ctcpname.equals_ci("VERSION")) { Module *enc = ModuleManager::FindFirstOf(ENCRYPTION); - IRCD->SendCTCP(bi, u->nick, "VERSION Anope-%s %s :%s - (%s) -- %s", Anope::Version().c_str(), Me->GetName().c_str(), IRCD->GetProtocolName().c_str(), enc ? enc->name.c_str() : "(none)", Anope::VersionBuildString().c_str()); + IRCD->SendNotice(bi, u->nick, Anope::FormatCTCP("VERSION", Anope::printf("Anope-%s %s :%s - (%s) -- %s", Anope::Version().c_str(), Me->GetName().c_str(), IRCD->GetProtocolName().c_str(), enc ? enc->name.c_str() : "(none)", Anope::VersionBuildString().c_str()))); } return; } diff --git a/src/misc.cpp b/src/misc.cpp index 8a0920fc1..0b760ea76 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -835,3 +835,50 @@ Anope::string Anope::Expand(const Anope::string &base, const Anope::string &frag return Anope::printf("%s%c%s", base.c_str(), separator, fragment.c_str()); } + +Anope::string Anope::FormatCTCP(const Anope::string &name, const Anope::string &value) +{ + if (value.empty()) + return Anope::printf("\1%s\1", name.c_str()); + + return Anope::printf("\1%s %s\1", name.c_str(), value.c_str()); +} + +bool Anope::ParseCTCP(const Anope::string &text, Anope::string &name, Anope::string &body) +{ + // According to draft-oakley-irc-ctcp-02 a valid CTCP must begin with SOH and + // contain at least one octet which is not NUL, SOH, CR, LF, or SPACE. As most + // of these are restricted at the protocol level we only need to check for SOH + // and SPACE. + if (text.length() < 2 || text[0] != '\x1' || text[1] == '\x1' || text[1] == ' ') + { + name.clear(); + body.clear(); + return false; + } + + auto end_of_name = text.find(' ', 2); + auto end_of_ctcp = *text.rbegin() == '\x1' ? 1 : 0; + if (end_of_name == std::string::npos) + { + // The CTCP only contains a name. + name = text.substr(1, text.length() - 1 - end_of_ctcp); + body.clear(); + return true; + } + + // The CTCP contains a name and a body. + name = text.substr(1, end_of_name - 1); + + auto start_of_body = text.find_first_not_of(' ', end_of_name + 1); + if (start_of_body == std::string::npos) + { + // The CTCP body is provided but empty. + body.clear(); + return true; + } + + // The CTCP body provided was non-empty. + body = text.substr(start_of_body, text.length() - start_of_body - end_of_ctcp); + return true; +} diff --git a/src/protocol.cpp b/src/protocol.cpp index 275721634..1c5edd2a4 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -166,12 +166,6 @@ void IRCDProto::SendGlobops(const MessageSource &source, const Anope::string &me Uplink::Send(source, "GLOBOPS", message); } -void IRCDProto::SendCTCPInternal(const MessageSource &source, const Anope::string &dest, const Anope::string &buf) -{ - Anope::string s = Anope::NormalizeBuffer(buf); - this->SendNotice(source, dest, "\1" + s + "\1"); -} - void IRCDProto::SendNumericInternal(int numeric, const Anope::string &dest, const std::vector<Anope::string> ¶ms) { Anope::string n = Anope::ToString(numeric); @@ -190,17 +184,6 @@ void IRCDProto::SendTopic(const MessageSource &source, Channel *c) Uplink::Send(source, "TOPIC", c->name, c->topic); } -void IRCDProto::SendAction(const MessageSource &source, const Anope::string &dest, const char *fmt, ...) -{ - va_list args; - char buf[BUFSIZE] = ""; - va_start(args, fmt); - vsnprintf(buf, BUFSIZE - 1, fmt, args); - va_end(args); - Anope::string actionbuf = Anope::string("\1ACTION ") + buf + '\1'; - SendPrivmsg(source, dest, actionbuf); -} - void IRCDProto::SendPing(const Anope::string &servname, const Anope::string &who) { if (servname.empty()) @@ -243,16 +226,6 @@ void IRCDProto::SendForceNickChange(User *u, const Anope::string &newnick, time_ Uplink::Send("SVSNICK", u->GetUID(), newnick, when); } -void IRCDProto::SendCTCP(const MessageSource &source, const Anope::string &dest, const char *fmt, ...) -{ - va_list args; - char buf[BUFSIZE] = ""; - va_start(args, fmt); - vsnprintf(buf, BUFSIZE - 1, fmt, args); - va_end(args); - SendCTCPInternal(source, dest, buf); -} - bool IRCDProto::IsNickValid(const Anope::string &nick) { /** |