summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSadie Powell <sadie@witchery.services>2024-06-24 14:29:55 +0100
committerSadie Powell <sadie@witchery.services>2024-06-24 14:29:55 +0100
commit693eeed762eac490b1289f8f4428ff0b5bbf1672 (patch)
tree974cbad287da04fc9e137d7f51092efd2e474acd
parent6e5713d64a379fc64c7ff6658362d02b3618c3ca (diff)
Rework how CTCP messages are sent and received.
-rw-r--r--include/anope.h14
-rw-r--r--include/protocol.h4
-rw-r--r--modules/botserv/bs_control.cpp6
-rw-r--r--modules/botserv/bs_kick.cpp8
-rw-r--r--modules/irc2sql/irc2sql.cpp41
-rw-r--r--src/messages.cpp14
-rw-r--r--src/misc.cpp47
-rw-r--r--src/protocol.cpp27
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> &param
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> &params)
{
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)
{
/**