From 089a0fd925be6be2090ff878120299750c008c2e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sun, 28 Jun 2015 16:19:32 +0200 Subject: [PATCH] Add cap-notify support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change alone doesn’t notify any capabilities to clients, but makes ZNC itself offer cap-notify and builds the foundations for various notifiable capabilities, such as away-notify, can be easily added. --- include/znc/Client.h | 27 +++++++++++++++- include/znc/IRCSock.h | 1 + src/Client.cpp | 71 ++++++++++++++++++++++++++++++++----------- src/IRCNetwork.cpp | 12 ++++++++ 4 files changed, 92 insertions(+), 19 deletions(-) diff --git a/include/znc/Client.h b/include/znc/Client.h index f8978a39..2feaacf0 100644 --- a/include/znc/Client.h +++ b/include/znc/Client.h @@ -95,6 +95,7 @@ public: m_bGotNick(false), m_bGotUser(false), m_bInCap(false), + m_bCapNotify(false), m_bNamesx(false), m_bUHNames(false), m_bAway(false), @@ -111,12 +112,24 @@ public: m_sNetwork(""), m_sIdentifier(""), m_spAuth(), - m_ssAcceptedCaps() + m_ssAcceptedCaps(), + m_mCoreCaps({{"multi-prefix", {false, [this](bool bVal) { m_bNamesx = bVal; }}}, + {"userhost-in-names", {false, [this](bool bVal) { m_bUHNames = bVal; }}}, + {"echo-message", {false, [this](bool bVal) { m_bEchoMessage = bVal; }}}, + {"server-time", {false, [this](bool bVal) { m_bServerTime = bVal; }}}, + {"batch", {false, [this](bool bVal) { m_bBatch = bVal; }}}, + {"cap-notify", {false, [this](bool bVal) { m_bCapNotify = bVal; }}}, + }) { EnableReadLine(); // RFC says a line can have 512 chars max, but we are // a little more gentle ;) SetMaxBufferThreshold(1024); + + // For compatibility with older clients + m_mCoreCaps["znc.in/server-time-iso"] = m_mCoreCaps["server-time"]; + m_mCoreCaps["znc.in/batch"] = m_mCoreCaps["batch"]; + m_mCoreCaps["znc.in/self-message"] = {false, [this](bool bVal) { m_bSelfMessage = bVal; }}; } virtual ~CClient(); @@ -131,6 +144,7 @@ public: CString GetNick(bool bAllowIRCNick = true) const; CString GetNickMask() const; CString GetIdentifier() const { return m_sIdentifier; } + bool HasCapNotify() const { return m_bCapNotify; } bool HasNamesx() const { return m_bNamesx; } bool HasUHNames() const { return m_bUHNames; } bool IsAway() const { return m_bAway; } @@ -160,6 +174,9 @@ public: bool IsCapEnabled(const CString& sCap) const { return 1 == m_ssAcceptedCaps.count(sCap); } + void NotifyServerDependentCaps(const SCString& ssCaps); + void ClearServerDependentCaps(); + void ReadLine(const CString& sData) override; bool SendMotd(); void HelpUser(const CString& sFilter = ""); @@ -191,6 +208,7 @@ protected: bool m_bGotNick; bool m_bGotUser; bool m_bInCap; + bool m_bCapNotify; bool m_bNamesx; bool m_bUHNames; bool m_bAway; @@ -208,6 +226,13 @@ protected: CString m_sIdentifier; std::shared_ptr m_spAuth; SCString m_ssAcceptedCaps; + // The capabilities supported by the ZNC core - capability names mapped + // to a pair which contains a bool describing whether the capability is + // server-dependent, and a capability value change handler. + std::map>> m_mCoreCaps; + // A subset of CIRCSock::GetAcceptedCaps(), the caps that can be listed + // in CAP LS and may be notified to the client with CAP NEW (cap-notify). + SCString m_ssServerDependentCaps; friend class ClientTest; }; diff --git a/include/znc/IRCSock.h b/include/znc/IRCSock.h index 4b3309c1..96073f62 100644 --- a/include/znc/IRCSock.h +++ b/include/znc/IRCSock.h @@ -104,6 +104,7 @@ public: const std::set& GetUserModes() const { return m_scUserModes; } // This is true if we are past raw 001 bool IsAuthed() const { return m_bAuthed; } + const SCString& GetAcceptedCaps() const { return m_ssAcceptedCaps; } bool IsCapAccepted(const CString& sCap) { return 1 == m_ssAcceptedCaps.count(sCap); } const MCString& GetISupport() const { return m_mISupport; } CString GetISupport(const CString& sKey, const CString& sDefault = "") const; diff --git a/src/Client.cpp b/src/Client.cpp index eb2e48e2..1c7af82a 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -874,27 +874,20 @@ void CClient::HandleCap(const CString& sLine) //TODO support ~ and = modifiers CString sSubCmd = sLine.Token(1); - std::map> mCoreCaps = { - {"multi-prefix", [this](bool bVal) { m_bNamesx = bVal; }}, - {"userhost-in-names", [this](bool bVal) { m_bUHNames = bVal; }}, - {"echo-message", [this](bool bVal) { m_bEchoMessage = bVal; }}, - {"server-time", [this](bool bVal) { m_bServerTime = bVal; }}, - {"batch", [this](bool bVal) { m_bBatch = bVal; }}, - }; - // For compatibility with older clients - mCoreCaps["znc.in/server-time-iso"] = mCoreCaps["server-time"]; - mCoreCaps["znc.in/batch"] = mCoreCaps["batch"]; - mCoreCaps["znc.in/self-message"] = [this](bool bVal) { m_bSelfMessage = bVal; }; - if (sSubCmd.Equals("LS")) { SCString ssOfferCaps; - for (const auto& it : mCoreCaps) { - ssOfferCaps.insert(it.first); + for (const auto& it : m_mCoreCaps) { + bool bServerDependent = std::get<0>(it.second); + if (!bServerDependent || m_ssServerDependentCaps.count(it.first) > 0) + ssOfferCaps.insert(it.first); } GLOBALMODULECALL(OnClientCapLs(this, ssOfferCaps), NOTHING); CString sRes = CString(" ").Join(ssOfferCaps.begin(), ssOfferCaps.end()); RespondCap("LS :" + sRes); m_bInCap = true; + if (sLine.Token(2).ToInt() >= 302) { + m_bCapNotify = true; + } } else if (sSubCmd.Equals("END")) { m_bInCap = false; if (!IsAttached()) { @@ -914,7 +907,12 @@ void CClient::HandleCap(const CString& sLine) if (sCap.TrimPrefix("-")) bVal = false; - bool bAccepted = mCoreCaps.count(sCap) > 0; + bool bAccepted = false; + const auto& it = m_mCoreCaps.find(sCap); + if (m_mCoreCaps.end() != it) { + bool bServerDependent = std::get<0>(it->second); + bAccepted = !bServerDependent || m_ssServerDependentCaps.count(sCap) > 0; + } GLOBALMODULECALL(IsClientCapSupported(this, sCap, bVal), &bAccepted); if (!bAccepted) { @@ -931,9 +929,10 @@ void CClient::HandleCap(const CString& sLine) if (sCap.TrimPrefix("-")) bVal = false; - auto handler_it = mCoreCaps.find(sCap); - if (mCoreCaps.end() != handler_it) { - handler_it->second(bVal); + auto handler_it = m_mCoreCaps.find(sCap); + if (m_mCoreCaps.end() != handler_it) { + const auto& handler = std::get<1>(handler_it->second); + handler(bVal); } GLOBALMODULECALL(OnClientCapRequest(this, sCap, bVal), NOTHING); @@ -996,3 +995,39 @@ void CClient::ParseIdentifier(const CString& sAuthLine) { m_sUser = sAuthLine; } } + +void CClient::NotifyServerDependentCaps(const SCString& ssCaps) +{ + for (const CString& sCap : ssCaps) { + const auto& it = m_mCoreCaps.find(sCap); + if (m_mCoreCaps.end() != it) { + bool bServerDependent = std::get<0>(it->second); + if (bServerDependent) { + m_ssServerDependentCaps.insert(sCap); + } + } + } + + if (HasCapNotify() && !m_ssServerDependentCaps.empty()) { + CString sCaps = CString(" ").Join(m_ssServerDependentCaps.begin(), m_ssServerDependentCaps.end()); + PutClient(":irc.znc.in CAP " + GetNick() + " NEW :" + sCaps); + } +} + +void CClient::ClearServerDependentCaps() +{ + if (HasCapNotify() && !m_ssServerDependentCaps.empty()) { + CString sCaps = CString(" ").Join(m_ssServerDependentCaps.begin(), m_ssServerDependentCaps.end()); + PutClient(":irc.znc.in CAP " + GetNick() + " DEL :" + sCaps); + + for (const CString& sCap : m_ssServerDependentCaps) { + const auto& it = m_mCoreCaps.find(sCap); + if (m_mCoreCaps.end() != it) { + const auto& handler = std::get<1>(it->second); + handler(false); + } + } + } + + m_ssServerDependentCaps.clear(); +} diff --git a/src/IRCNetwork.cpp b/src/IRCNetwork.cpp index 1948f546..a2f65e73 100644 --- a/src/IRCNetwork.cpp +++ b/src/IRCNetwork.cpp @@ -567,6 +567,10 @@ void CIRCNetwork::ClientConnected(CClient *pClient) { size_t uIdx, uSize; + if (m_pIRCSock) { + pClient->NotifyServerDependentCaps(m_pIRCSock->GetAcceptedCaps()); + } + pClient->SetPlaybackActive(true); if (m_RawBuffer.IsEmpty()) { @@ -659,6 +663,7 @@ void CIRCNetwork::ClientDisconnected(CClient *pClient) { if (it != m_vClients.end()) { m_vClients.erase(it); } + pClient->ClearServerDependentCaps(); } CUser* CIRCNetwork::GetUser() const { @@ -1223,6 +1228,10 @@ void CIRCNetwork::SetIRCSocket(CIRCSock* pIRCSock) { } void CIRCNetwork::IRCConnected() { + const SCString& ssCaps = m_pIRCSock->GetAcceptedCaps(); + for (CClient* pClient : m_vClients) { + pClient->NotifyServerDependentCaps(ssCaps); + } if (m_uJoinDelay > 0) { m_pJoinTimer->Delay(m_uJoinDelay); } else { @@ -1231,6 +1240,9 @@ void CIRCNetwork::IRCConnected() { } void CIRCNetwork::IRCDisconnected() { + for (CClient* pClient : m_vClients) { + pClient->ClearServerDependentCaps(); + } m_pIRCSock = nullptr; SetIRCServer("");