From b57d794aaf34f1505a6f55670de7594596b5a031 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sun, 8 Jan 2023 15:32:27 +0000 Subject: [PATCH] Send multiline CAP LS response for IRCv3.2 clients --- include/znc/Client.h | 2 ++ src/Client.cpp | 40 ++++++++++++++++++++++++++----- test/integration/tests/core.cpp | 42 +++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/include/znc/Client.h b/include/znc/Client.h index 5428cf44..af09e4aa 100644 --- a/include/znc/Client.h +++ b/include/znc/Client.h @@ -103,6 +103,7 @@ class CClient : public CIRCSocket { m_bGotPass(false), m_bGotNick(false), m_bGotUser(false), + m_bCap302(false), m_bInCap(false), m_bCapNotify(false), m_bAwayNotify(false), @@ -349,6 +350,7 @@ class CClient : public CIRCSocket { bool m_bGotPass; bool m_bGotNick; bool m_bGotUser; + bool m_bCap302; bool m_bInCap; bool m_bCapNotify; bool m_bAwayNotify; diff --git a/src/Client.cpp b/src/Client.cpp index 6d215218..44ec66be 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -687,6 +687,21 @@ void CClient::RespondCap(const CString& sResponse) { PutClient(":irc.znc.in CAP " + GetNick() + " " + sResponse); } +static VCString MultiLine(const SCString& ssCaps) { + VCString vsRes = {""}; + for (const CString& sCap : ssCaps) { + if (vsRes.back().length() + sCap.length() > 400) { + vsRes.push_back(sCap); + } else { + if (!vsRes.back().empty()) { + vsRes.back() += " "; + } + vsRes.back() += sCap; + } + } + return vsRes; +} + void CClient::HandleCap(const CMessage& Message) { CString sSubCmd = Message.GetParam(0); @@ -699,12 +714,18 @@ void CClient::HandleCap(const CMessage& Message) { ssOfferCaps.insert(it.first); } GLOBALMODULECALL(OnClientCapLs(this, ssOfferCaps), NOTHING); - CString sRes = - CString(" ").Join(ssOfferCaps.begin(), ssOfferCaps.end()); - RespondCap("LS :" + sRes); + VCString vsCaps = MultiLine(ssOfferCaps); m_bInCap = true; if (Message.GetParam(1).ToInt() >= 302) { + m_bCap302 = true; m_bCapNotify = true; + for (int i = 0; i < vsCaps.size() - 1; ++i) { + RespondCap("LS * :" + vsCaps[i]); + } + RespondCap("LS :" + vsCaps.back()); + } else { + // Can't send more than one line of caps :( + RespondCap("LS :" + vsCaps.front()); } } else if (sSubCmd.Equals("END")) { m_bInCap = false; @@ -763,9 +784,16 @@ void CClient::HandleCap(const CMessage& Message) { RespondCap("ACK :" + Message.GetParam(1)); } else if (sSubCmd.Equals("LIST")) { - CString sList = - CString(" ").Join(m_ssAcceptedCaps.begin(), m_ssAcceptedCaps.end()); - RespondCap("LIST :" + sList); + VCString vsCaps = MultiLine(m_ssAcceptedCaps); + if (m_bCap302) { + for (int i = 0; i < vsCaps.size() - 1; ++i) { + RespondCap("LIST * :" + vsCaps[i]); + } + RespondCap("LIST :" + vsCaps.back()); + } else { + // Can't send more than one line of caps :( + RespondCap("LISTS :" + vsCaps.front()); + } } else { PutClient(":irc.znc.in 410 " + GetNick() + " " + sSubCmd + " :Invalid CAP subcommand"); diff --git a/test/integration/tests/core.cpp b/test/integration/tests/core.cpp index a0c23a4b..8f9618f8 100644 --- a/test/integration/tests/core.cpp +++ b/test/integration/tests/core.cpp @@ -165,6 +165,7 @@ TEST_F(ZNCTest, InvalidConfigInChan) { auto znc = Run(); znc->ShouldFinishItself(1); } + TEST_F(ZNCTest, Encoding) { auto znc = Run(); auto ircd = ConnectIRCd(); @@ -430,5 +431,46 @@ TEST_F(ZNCTest, DenyOptions) { client2.ReadUntil("Access denied!"); } +TEST_F(ZNCTest, CAP302MultiLS) { + auto znc = Run(); + auto ircd = ConnectIRCd(); + auto client = LoginClient(); + InstallModule("testmod.cpp", R"( + #include + #include + class TestModule : public CModule { + public: + MODCONSTRUCTOR(TestModule) {} + void OnClientCapLs(CClient* pClient, SCString& ssCaps) override { + for (int i = 0; i < 100; ++i) { + ssCaps.insert("testcap-" + CString(i)); + } + } + }; + GLOBALMODULEDEFS(TestModule, "Test") + )"); + client.Write("znc loadmod testmod"); + client.ReadUntil("Loaded module testmod"); + + auto client2 = ConnectClient(); + client2.Write("CAP LS"); + client2.ReadUntil("LS :"); + auto rem = client2.ReadRemainder(); + ASSERT_GT(rem.indexOf("testcap-10"), 10); + ASSERT_EQ(rem.indexOf("testcap-80"), -1); + ASSERT_EQ(rem.indexOf("LS"), -1); + + client2 = ConnectClient(); + client2.Write("CAP LS 302"); + client2.ReadUntil("LS * :"); + rem = client2.ReadRemainder(); + qsizetype w = 0; + ASSERT_GT(w = rem.indexOf("testcap-10"), 1); + ASSERT_GT(w = rem.indexOf("testcap-22", w), 1); + ASSERT_GT(w = rem.indexOf("LS * :", w), 1); + ASSERT_GT(rem.indexOf("testcap-80", w), 1); + ASSERT_GT(rem.indexOf("LS :", w), 1); +} + } // namespace } // namespace znc_inttest