Merge pull request #1859 from DarthGandalf/cap302

Cap 3.2
This commit is contained in:
Alexey Sokolov
2024-01-26 02:06:53 +00:00
committed by GitHub
22 changed files with 986 additions and 235 deletions
+13 -81
View File
@@ -98,77 +98,7 @@ class CClientAuth : public CAuthBase {
class CClient : public CIRCSocket {
public:
CClient()
: CIRCSocket(),
m_bGotPass(false),
m_bGotNick(false),
m_bGotUser(false),
m_bInCap(false),
m_bCapNotify(false),
m_bAwayNotify(false),
m_bAccountNotify(false),
m_bAccountTag(false),
m_bExtendedJoin(false),
m_bNamesx(false),
m_bUHNames(false),
m_bAway(false),
m_bServerTime(false),
m_bBatch(false),
m_bEchoMessage(false),
m_bSelfMessage(false),
m_bPlaybackActive(false),
m_pUser(nullptr),
m_pNetwork(nullptr),
m_sNick("unknown-nick"),
m_sPass(""),
m_sUser(""),
m_sNetwork(""),
m_sIdentifier(""),
m_spAuth(),
m_ssAcceptedCaps(),
m_ssSupportedTags(),
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;
SetTagSupport("time", bVal);
}}},
{"batch", {false, [this](bool bVal) {
m_bBatch = bVal;
SetTagSupport("batch", bVal);
}}},
{"cap-notify",
{false, [this](bool bVal) { m_bCapNotify = bVal; }}},
{"away-notify",
{true, [this](bool bVal) { m_bAwayNotify = bVal; }}},
{"account-notify",
{true, [this](bool bVal) { m_bAccountNotify = bVal; }}},
{"account-tag",
{true, [this](bool bVal) {
m_bAccountTag = bVal;
SetTagSupport("account", bVal);
}}},
{"extended-join",
{true, [this](bool bVal) { m_bExtendedJoin = 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; }};
}
CClient();
virtual ~CClient();
CClient(const CClient&) = delete;
@@ -181,6 +111,8 @@ class CClient : public CIRCSocket {
CString GetNick(bool bAllowIRCNick = true) const;
CString GetNickMask() const;
CString GetIdentifier() const { return m_sIdentifier; }
unsigned short int CapVersion() const { return m_uCapVersion; }
bool HasCap302() const { return CapVersion() >= 302; }
bool HasCapNotify() const { return m_bCapNotify; }
bool HasAwayNotify() const { return m_bAwayNotify; }
bool HasAccountNotify() const { return m_bAccountNotify; }
@@ -293,8 +225,9 @@ class CClient : public CIRCSocket {
*/
void SetTagSupport(const CString& sTag, bool bState);
void NotifyServerDependentCaps(const SCString& ssCaps);
void ClearServerDependentCaps();
/** Notifies client about one specific cap which server has just notified us about.
*/
void NotifyServerDependentCap(const CString& sCap, bool bValue, const CString& sValue);
void ReadLine(const CString& sData) override;
bool SendMotd();
@@ -349,6 +282,7 @@ class CClient : public CIRCSocket {
bool m_bGotPass;
bool m_bGotNick;
bool m_bGotUser;
unsigned short int m_uCapVersion;
bool m_bInCap;
bool m_bCapNotify;
bool m_bAwayNotify;
@@ -373,16 +307,14 @@ class CClient : public CIRCSocket {
std::shared_ptr<CAuthBase> m_spAuth;
SCString m_ssAcceptedCaps;
SCString m_ssSupportedTags;
// 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<CString, std::pair<bool, std::function<void(bool bVal)>>>
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;
// The capabilities supported by the ZNC core - capability names mapped to
// change handler. Note: this lists caps which don't require support on IRC
// server.
static const std::map<CString, std::function<void(CClient*, bool bVal)>>&
CoreCaps();
friend class ClientTest;
friend class CCoreCaps;
};
#endif // !ZNC_CLIENT_H
+2
View File
@@ -156,6 +156,8 @@ class CIRCNetwork : private CCoreTranslationMixin {
void IRCConnected();
void IRCDisconnected();
void CheckIRCConnect();
void NotifyClientsAboutServerDependentCap(const CString& sCap, bool bValue);
bool IsServerCapAccepted(const CString& sCap) const;
bool PutIRC(const CString& sLine);
bool PutIRC(const CMessage& Message);
+5 -1
View File
@@ -162,6 +162,8 @@ class CIRCSock : public CIRCSocket {
bool IsCapAccepted(const CString& sCap) {
return 1 == m_ssAcceptedCaps.count(sCap);
}
CString GetCapLsValue(const CString& sKey,
const CString& sDefault = "") const;
const MCString& GetISupport() const { return m_mISupport; }
CString GetISupport(const CString& sKey,
const CString& sDefault = "") const;
@@ -192,7 +194,7 @@ class CIRCSock : public CIRCSocket {
bool OnTextMessage(CTextMessage& Message);
bool OnTopicMessage(CTopicMessage& Message);
bool OnWallopsMessage(CMessage& Message);
bool OnServerCapAvailable(const CString& sCap);
bool OnServerCapAvailable(const CString& sCap, const CString& sValue);
// !Message Handlers
void SetNick(const CString& sNick);
@@ -223,6 +225,7 @@ class CIRCSock : public CIRCSocket {
unsigned int m_uCapPaused;
SCString m_ssAcceptedCaps;
SCString m_ssPendingCaps;
MCString m_msCapLsValues;
time_t m_lastCTCP;
unsigned int m_uNumCTCP;
static const time_t m_uCTCPFloodTime;
@@ -237,6 +240,7 @@ class CIRCSock : public CIRCSocket {
VCString m_vsSSLError;
friend class CIRCFloodTimer;
friend class CCoreCaps;
};
#endif // !ZNC_IRCSOCK_H
+79 -1
View File
@@ -25,6 +25,7 @@
#include <znc/main.h>
#include <znc/Translation.h>
#include <functional>
#include <memory>
#include <set>
#include <queue>
#include <sys/time.h>
@@ -166,6 +167,19 @@ class CFPTimer;
class CSockManager;
// !Forward Declarations
class CCapability {
public:
virtual ~CCapability() = default;
virtual void OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) {}
virtual void OnClientChangedSupport(CClient* pClient, bool bState) {}
CModule* GetModule() { return m_pModule; }
void SetModule(CModule* p) { m_pModule = p; }
protected:
CModule* m_pModule = nullptr;
};
class CTimer : public CCron {
public:
CTimer(CModule* pModule, unsigned int uInterval, unsigned int uCycles,
@@ -998,13 +1012,28 @@ class CModule {
virtual EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic);
/** Called for every CAP received via CAP LS from server.
* If you need to also advertise the cap to clients, use
* AddServerDependentCapability() instead.
* @param sCap capability supported by server.
* @return true if your module supports this CAP and
* needs to turn it on with CAP REQ.
*/
virtual bool OnServerCapAvailable(const CString& sCap);
/** Called for every CAP received via CAP LS from server.
* By default just calls OnServerCapAvailable() without sValue, so
* overriding one of the two is enough.
* If you need to also advertise the cap to clients, use
* AddServerDependentCapability() instead.
* @param sCap capability name supported by server.
* @param sValue value.
* @return true if your module supports this CAP and
* needs to turn it on with CAP REQ.
*/
virtual bool OnServerCap302Available(const CString& sCap, const CString& sValue);
/** Called for every CAP accepted or rejected by server
* (with CAP ACK or CAP NAK after our CAP REQ).
* If you need to also advertise the cap to clients, use
* AddServerDependentCapability() instead.
* @param sCap capability accepted/rejected by server.
* @param bSuccess true if capability was accepted, false if rejected.
*/
@@ -1283,8 +1312,32 @@ class CModule {
virtual EModRet OnUnknownUserRaw(CClient* pClient, CString& sLine);
virtual EModRet OnUnknownUserRawMessage(CMessage& Message);
/** Called after login, and also during JumpNetwork. */
virtual void OnClientAttached();
/** Called upon disconnect, and also during JumpNetwork. */
virtual void OnClientDetached();
#ifndef SWIG
/** Simple API to support client capabilities which depend on server to support that capability.
* It is built on top of other CAP related API, but removes boilerplate,
* and handles some tricky cases related to cap-notify and JumpNetwork. To
* use, create a subclass of CCapability, and pass to this function; it
* will automatically set the module pointer, then call the callbacks to
* notify you when server and client accepted support of the capability, or
* stopped supporting it. Note that it's not a strict toggle: e.g.
* sometimes client will disable the cap even when it was already disabled
* for that client.
* For perl and python modules, this function accepts 3 parameters:
* name, server callback, client callback; signatures of the callbacks are
* the same as of the virtual functions you'd implement in C++.
*/
void AddServerDependentCapability(const CString& sName, std::unique_ptr<CCapability> pCap);
#endif
/** Called when a client told us CAP LS. Use ssCaps.insert("cap-name")
* for announcing capabilities which your module supports.
* If you need to adverite the cap to clients only when it's also supported
* by the server, use AddServerDependentCapability() instead.
* @param pClient The client which requested the list.
* @param ssCaps set of caps which will be sent to client.
*/
@@ -1298,6 +1351,8 @@ class CModule {
virtual bool IsClientCapSupported(CClient* pClient, const CString& sCap,
bool bState);
/** Called when we actually need to turn a capability on or off for a client.
* If you need to adverite the cap to clients only when it's also supported
* by the server, use AddServerDependentCapability() instead.
* If implementing a custom capability, make sure to call
* pClient->SetTagSupport("tag-name", bState) for each tag that the
* capability provides.
@@ -1359,6 +1414,26 @@ class CModule {
const CString& sContext = "") const;
#endif
// Default implementations of several callbacks to make
// AddServerDependentCapability work in modpython/modperl.
// Don't worry about existence of these functions.
bool InternalServerDependentCapsOnServerCap302Available(
const CString& sCap, const CString& sValue);
void InternalServerDependentCapsOnServerCapResult(const CString& sCap,
bool bSuccess);
void InternalServerDependentCapsOnClientCapLs(CClient* pClient,
SCString& ssCaps);
bool InternalServerDependentCapsIsClientCapSupported(CClient* pClient,
const CString& sCap,
bool bState);
void InternalServerDependentCapsOnClientCapRequest(CClient* pClient,
const CString& sCap,
bool bState);
void InternalServerDependentCapsOnClientAttached();
void InternalServerDependentCapsOnClientDetached();
void InternalServerDependentCapsOnIRCConnected();
void InternalServerDependentCapsOnIRCDisconnected();
protected:
CModInfo::EModuleType m_eType;
CString m_sDescription;
@@ -1378,6 +1453,7 @@ class CModule {
CString m_sArgs;
CString m_sModPath;
CTranslationDomainRefHolder m_Translation;
std::map<CString, std::unique_ptr<CCapability>> m_mServerDependentCaps;
private:
MCString
@@ -1541,8 +1617,10 @@ class CModules : public std::vector<CModule*>, private CCoreTranslationMixin {
bool OnSendToClientMessage(CMessage& Message);
bool OnSendToIRC(CString& sLine);
bool OnSendToIRCMessage(CMessage& Message);
bool OnClientAttached();
bool OnClientDetached();
bool OnServerCapAvailable(const CString& sCap);
bool OnServerCapAvailable(const CString& sCap, const CString& sValue);
bool OnServerCapResult(const CString& sCap, bool bSuccess);
CModule* FindModule(const CString& sModule) const;
+80
View File
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2004-2024 ZNC, see the NOTICE file for details.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <znc/Client.h>
#include <znc/IRCNetwork.h>
#include <znc/IRCSock.h>
#include <znc/Modules.h>
#include <memory>
class CCoreCaps : public CModule {
// Note: for historical reasons CClient and CIRCSock have such fields, but
// really they should not.
// TODO: move these fields and their handling from core to this module.
class AwayNotify : public CCapability {
void OnServerChangedSupport(CIRCNetwork* pNetwork,
bool bState) override {
pNetwork->GetIRCSock()->m_bAwayNotify = bState;
}
void OnClientChangedSupport(CClient* pClient, bool bState) override {
pClient->m_bAwayNotify = bState;
}
};
class AccountNotify : public CCapability {
void OnServerChangedSupport(CIRCNetwork* pNetwork,
bool bState) override {
pNetwork->GetIRCSock()->m_bAccountNotify = bState;
}
void OnClientChangedSupport(CClient* pClient, bool bState) override {
pClient->m_bAccountNotify = bState;
}
};
class AccountTag : public CCapability {
void OnServerChangedSupport(CIRCNetwork* pNetwork,
bool bState) override {
pNetwork->GetIRCSock()->m_bAccountTag = bState;
}
void OnClientChangedSupport(CClient* pClient, bool bState) override {
pClient->m_bAccountTag = bState;
pClient->SetTagSupport("account", bState);
}
};
class ExtendedJoin : public CCapability {
void OnServerChangedSupport(CIRCNetwork* pNetwork,
bool bState) override {
pNetwork->GetIRCSock()->m_bExtendedJoin = bState;
}
void OnClientChangedSupport(CClient* pClient, bool bState) override {
pClient->m_bExtendedJoin = bState;
}
};
public:
MODCONSTRUCTOR(CCoreCaps) {
AddServerDependentCapability("away-notify", std::make_unique<AwayNotify>());
AddServerDependentCapability("account-notify", std::make_unique<AccountNotify>());
AddServerDependentCapability("account-tag", std::make_unique<AccountTag>());
AddServerDependentCapability("extended-join", std::make_unique<ExtendedJoin>());
}
};
GLOBALMODULEDEFS(
CCoreCaps,
t_s("Adds support for several IRC capabilities, extracted from ZNC core."))
+35
View File
@@ -374,6 +374,41 @@ CPerlSocket::~CPerlSocket() {
}
}
CPerlCapability::~CPerlCapability() {
SvREFCNT_dec(m_serverCb);
SvREFCNT_dec(m_clientCb);
}
void CPerlCapability::OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) {
PSTART;
PUSH_PTR(CIRCNetwork*, pNetwork);
mXPUSHi(bState);
PUTBACK;
ret = call_sv(m_serverCb, G_EVAL | G_ARRAY);
SPAGAIN;
SP -= ret;
ax = (SP - PL_stack_base) + 1;
if (SvTRUE(ERRSV)) {
DEBUG("Perl hook OnServerChangedSupport died with: " + PString(ERRSV));
}
PEND;
}
void CPerlCapability::OnClientChangedSupport(CClient* pClient, bool bState) {
PSTART;
PUSH_PTR(CClient*, pClient);
mXPUSHi(bState);
PUTBACK;
ret = call_sv(m_clientCb, G_EVAL | G_ARRAY);
SPAGAIN;
SP -= ret;
ax = (SP - PL_stack_base) + 1;
if (SvTRUE(ERRSV)) {
DEBUG("Perl hook OnServerChangedSupport died with: " + PString(ERRSV));
}
PEND;
}
template <>
void TModInfo<CModPerl>(CModInfo& Info) {
Info.SetWikiPage("modperl");
+3
View File
@@ -61,7 +61,10 @@ EModRet OnPrivNotice(CNick& Nick, CString& sMessage)
EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage)
EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic)
bool OnServerCapAvailable(const CString& sCap)
bool OnServerCap302Available(const CString& sCap, const CString& sValue)
void OnServerCapResult(const CString& sCap, bool bSuccess)
void OnClientAttached()
void OnClientDetached()
EModRet OnTimerAutoJoin(CChan& Channel)
bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl)
EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet)
+4
View File
@@ -189,6 +189,10 @@ class MCString : public std::map<CString, CString> {};
bool ExistsNV(const CString& sName) {
return $self->EndNV() != $self->FindNV(sName);
}
void AddServerDependentCapability(const CString& sName, SV* serverCb,
SV* clientCb) {
$self->AddServerDependentCapability(sName, std::make_unique<CPerlCapability>(serverCb, clientCb));
}
}
%extend CModules {
+17
View File
@@ -116,7 +116,10 @@ class ZNC_EXPORT_LIB_EXPORT CPerlModule : public CModule {
CString& sMessage) override;
EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) override;
bool OnServerCapAvailable(const CString& sCap) override;
bool OnServerCap302Available(const CString& sCap, const CString& sValue) override;
void OnServerCapResult(const CString& sCap, bool bSuccess) override;
void OnClientAttached() override;
void OnClientDetached() override;
EModRet OnTimerAutoJoin(CChan& Channel) override;
bool OnEmbeddedWebRequest(CWebSock&, const CString&, CTemplate&) override;
EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) override;
@@ -213,6 +216,20 @@ inline CPerlSocket* CreatePerlSocket(CPerlModule* pModule, SV* perlObj) {
return new CPerlSocket(pModule, perlObj);
}
class ZNC_EXPORT_LIB_EXPORT CPerlCapability : public CCapability {
public:
CPerlCapability(SV* serverCb, SV* clientCb)
: m_serverCb(newSVsv(serverCb)), m_clientCb(newSVsv(clientCb)) {}
~CPerlCapability();
void OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) override;
void OnClientChangedSupport(CClient* pClient, bool bState) override;
private:
SV* m_serverCb;
SV* m_clientCb;
};
inline bool HaveIPv6() {
#ifdef HAVE_IPV6
return true;
+3
View File
@@ -405,6 +405,9 @@ sub OnPrivNotice {}
sub OnChanNotice {}
sub OnTopic {}
sub OnServerCapAvailable {}
sub OnServerCap302Available { my ($self, $cap, $value) = @_; $self->OnServerCapAvailable($cap) }
sub OnClientAttached {}
sub OnClientDetached {}
sub OnServerCapResult {}
sub OnTimerAutoJoin {}
sub OnEmbeddedWebRequest {}
+43
View File
@@ -478,6 +478,49 @@ CPySocket::~CPySocket() {
Py_CLEAR(m_pyObj);
}
CPyCapability::CPyCapability(PyObject* serverCb, PyObject* clientCb)
: m_serverCb(serverCb), m_clientCb(clientCb) {
Py_INCREF(serverCb);
Py_INCREF(clientCb);
}
CPyCapability::~CPyCapability() {
Py_CLEAR(m_serverCb);
Py_CLEAR(m_clientCb);
}
void CPyCapability::OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) {
PyObject* pyArg_Network =
SWIG_NewInstanceObj(pNetwork, SWIG_TypeQuery("CIRCNetwork*"), 0);
PyObject* pyArg_bState = Py_BuildValue("l", (long int)bState);
PyObject* pyRes = PyObject_CallFunctionObjArgs(m_serverCb, pyArg_Network,
pyArg_bState, nullptr);
if (!pyRes) {
CString sPyErr = ((CPyModule*)GetModule())->GetPyExceptionStr();
DEBUG("modpython: " << GetModule()->GetModName()
<< "/OnServerChangedSupport failed: " << sPyErr);
}
Py_CLEAR(pyRes);
Py_CLEAR(pyArg_bState);
Py_CLEAR(pyArg_Network);
}
void CPyCapability::OnClientChangedSupport(CClient* pClient, bool bState) {
PyObject* pyArg_Client =
SWIG_NewInstanceObj(pClient, SWIG_TypeQuery("CClient*"), 0);
PyObject* pyArg_bState = Py_BuildValue("l", (long int)bState);
PyObject* pyRes = PyObject_CallFunctionObjArgs(m_clientCb, pyArg_Client,
pyArg_bState, nullptr);
if (!pyRes) {
CString sPyErr = ((CPyModule*)GetModule())->GetPyExceptionStr();
DEBUG("modpython: " << GetModule()->GetModName()
<< "/OnClientChangedSupport failed: " << sPyErr);
}
Py_CLEAR(pyRes);
Py_CLEAR(pyArg_bState);
Py_CLEAR(pyArg_Client);
}
CPyModule* CPyModCommand::GetModule() {
return this->m_pModule;
}
+3
View File
@@ -61,7 +61,10 @@ EModRet OnPrivNotice(CNick& Nick, CString& sMessage)
EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage)
EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic)
bool OnServerCapAvailable(const CString& sCap)
bool OnServerCap302Available(const CString& sCap, const CString& sValue)
void OnServerCapResult(const CString& sCap, bool bSuccess)
void OnClientAttached()
void OnClientDetached()
EModRet OnTimerAutoJoin(CChan& Channel)
bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl)
EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet)
+4
View File
@@ -241,6 +241,10 @@ class CPyRetBool {
bool ExistsNV(const CString& sName) {
return $self->EndNV() != $self->FindNV(sName);
}
void AddServerDependentCapability(const CString& sName, PyObject* serverCb,
PyObject* clientCb) {
$self->AddServerDependentCapability(sName, std::make_unique<CPyCapability>(serverCb, clientCb));
}
}
%extend CModules {
+17
View File
@@ -136,7 +136,10 @@ class ZNC_EXPORT_LIB_EXPORT CPyModule : public CModule {
CString& sMessage) override;
EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) override;
bool OnServerCapAvailable(const CString& sCap) override;
bool OnServerCap302Available(const CString& sCap, const CString& sValue) override;
void OnServerCapResult(const CString& sCap, bool bSuccess) override;
void OnClientAttached() override;
void OnClientDetached() override;
EModRet OnTimerAutoJoin(CChan& Channel) override;
bool OnEmbeddedWebRequest(CWebSock&, const CString&, CTemplate&) override;
EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) override;
@@ -366,3 +369,17 @@ inline CPyModCommand* CreatePyModCommand(CPyModule* pModule,
PyObject* pyObj) {
return new CPyModCommand(pModule, sCmd, sArgs, sDesc, pyObj);
}
class ZNC_EXPORT_LIB_EXPORT CPyCapability : public CCapability {
public:
CPyCapability(PyObject* serverCb, PyObject* clientCb);
~CPyCapability();
void OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) override;
void OnClientChangedSupport(CClient* pClient, bool bState) override;
private:
PyObject* m_serverCb;
PyObject* m_clientCb;
};
+9
View File
@@ -410,6 +410,15 @@ class Module:
def OnServerCapAvailable(self, sCap):
pass
def OnServerCap302Available(self, sCap, sValue):
return self.OnServerCapAvailable(sCap)
def OnClientAttached(self):
pass
def OnClientDetached(self):
pass
def OnServerCapResult(self, sCap, bSuccess):
pass
+130 -56
View File
@@ -75,6 +75,41 @@ using std::vector;
} \
}
CClient::CClient() : CIRCSocket(),
m_bGotPass(false),
m_bGotNick(false),
m_bGotUser(false),
m_uCapVersion(0),
m_bInCap(false),
m_bCapNotify(false),
m_bAwayNotify(false),
m_bAccountNotify(false),
m_bAccountTag(false),
m_bExtendedJoin(false),
m_bNamesx(false),
m_bUHNames(false),
m_bAway(false),
m_bServerTime(false),
m_bBatch(false),
m_bEchoMessage(false),
m_bSelfMessage(false),
m_bPlaybackActive(false),
m_pUser(nullptr),
m_pNetwork(nullptr),
m_sNick("unknown-nick"),
m_sPass(""),
m_sUser(""),
m_sNetwork(""),
m_sIdentifier(""),
m_spAuth(),
m_ssAcceptedCaps(),
m_ssSupportedTags() {
EnableReadLine();
// RFC says a line can have 512 chars max, but we are
// a little more gentle ;)
SetMaxBufferThreshold(1024);
}
CClient::~CClient() {
if (m_spAuth) {
CClientAuth* pAuth = (CClientAuth*)&(*m_spAuth);
@@ -236,7 +271,7 @@ void CClient::SetNetwork(CIRCNetwork* pNetwork, bool bDisconnect,
m_pNetwork->ClientDisconnected(this);
if (bDisconnect) {
ClearServerDependentCaps();
NETWORKMODULECALL(OnClientDetached(), m_pUser, m_pNetwork, this, NOTHING);
// Tell the client they are no longer in these channels.
const vector<CChan*>& vChans = m_pNetwork->GetChans();
for (const CChan* pChan : vChans) {
@@ -258,6 +293,7 @@ void CClient::SetNetwork(CIRCNetwork* pNetwork, bool bDisconnect,
} else if (m_pUser) {
m_pUser->UserConnected(this);
}
NETWORKMODULECALL(OnClientAttached(), m_pUser, m_pNetwork, this, NOTHING);
}
}
@@ -691,24 +727,77 @@ 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;
}
const std::map<CString, std::function<void(CClient*, bool bVal)>>&
CClient::CoreCaps() {
static const std::map<CString, std::function<void(CClient*, bool bVal)>> mCoreCaps = []{
std::map<CString, std::function<void(CClient*, bool bVal)>> mCoreCaps = {
{"multi-prefix",
[](CClient* pClient, bool bVal) { pClient->m_bNamesx = bVal; }},
{"userhost-in-names",
[](CClient* pClient, bool bVal) { pClient->m_bUHNames = bVal; }},
{"echo-message",
[](CClient* pClient, bool bVal) { pClient->m_bEchoMessage = bVal; }},
{"server-time",
[](CClient* pClient, bool bVal) {
pClient->m_bServerTime = bVal;
pClient->SetTagSupport("time", bVal);
}},
{"batch", [](CClient* pClient, bool bVal) {
pClient->m_bBatch = bVal;
pClient->SetTagSupport("batch", bVal);
}},
{"cap-notify",
[](CClient* pClient, bool bVal) { pClient->m_bCapNotify = 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"] = [](CClient* pClient, bool bVal) {
pClient->m_bSelfMessage = bVal;
};
return mCoreCaps;
}();
return mCoreCaps;
}
void CClient::HandleCap(const CMessage& Message) {
CString sSubCmd = Message.GetParam(0);
if (sSubCmd.Equals("LS")) {
m_uCapVersion = std::max(m_uCapVersion, Message.GetParam(1).ToUShort());
SCString ssOfferCaps;
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);
for (const auto& it : CoreCaps()) {
ssOfferCaps.insert(it.first);
}
GLOBALMODULECALL(OnClientCapLs(this, ssOfferCaps), NOTHING);
CString sRes =
CString(" ").Join(ssOfferCaps.begin(), ssOfferCaps.end());
RespondCap("LS :" + sRes);
NETWORKMODULECALL(OnClientCapLs(this, ssOfferCaps), GetUser(), GetNetwork(), this, NOTHING);
VCString vsCaps = MultiLine(ssOfferCaps);
m_bInCap = true;
if (Message.GetParam(1).ToInt() >= 302) {
if (HasCap302()) {
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;
@@ -729,13 +818,11 @@ void CClient::HandleCap(const CMessage& Message) {
if (sCap.TrimPrefix("-")) bVal = false;
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;
auto it = CoreCaps().find(sCap);
if (CoreCaps().end() != it) {
bAccepted = true;
}
GLOBALMODULECALL(IsClientCapSupported(this, sCap, bVal),
NETWORKMODULECALL(IsClientCapSupported(this, sCap, bVal), GetUser(), GetNetwork(), this,
&bAccepted);
if (!bAccepted) {
@@ -751,12 +838,12 @@ void CClient::HandleCap(const CMessage& Message) {
CString sCap = sToken;
if (sCap.TrimPrefix("-")) bVal = false;
auto handler_it = m_mCoreCaps.find(sCap);
if (m_mCoreCaps.end() != handler_it) {
const auto& handler = std::get<1>(handler_it->second);
handler(bVal);
auto handler_it = CoreCaps().find(sCap);
if (CoreCaps().end() != handler_it) {
const auto& handler = handler_it->second;
handler(this, bVal);
}
GLOBALMODULECALL(OnClientCapRequest(this, sCap, bVal), NOTHING);
NETWORKMODULECALL(OnClientCapRequest(this, sCap, bVal), GetUser(), GetNetwork(), this, NOTHING);
if (bVal) {
m_ssAcceptedCaps.insert(sCap);
@@ -767,9 +854,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 (HasCap302()) {
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");
@@ -828,41 +922,21 @@ void CClient::SetTagSupport(const CString& sTag, bool bState) {
}
}
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);
void CClient::NotifyServerDependentCap(const CString& sCap, bool bValue, const CString& sValue) {
if (bValue) {
if (HasCapNotify()) {
if (HasCap302() && !sValue.empty()) {
PutClient(":irc.znc.in CAP " + GetNick() + " NEW :" + sCap + "=" + sValue);
} else {
PutClient(":irc.znc.in CAP " + GetNick() + " NEW :" + 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_ssAcceptedCaps.erase(sCap);
} else {
if (HasCapNotify()) {
PutClient(":irc.znc.in CAP " + GetNick() + " DEL :" + sCap);
}
m_ssAcceptedCaps.erase(sCap);
}
m_ssServerDependentCaps.clear();
}
template <typename T>
+11 -11
View File
@@ -660,10 +660,6 @@ void CIRCNetwork::ClientConnected(CClient* pClient) {
size_t uIdx, uSize;
if (m_pIRCSock) {
pClient->NotifyServerDependentCaps(m_pIRCSock->GetAcceptedCaps());
}
pClient->SetPlaybackActive(true);
if (m_RawBuffer.IsEmpty()) {
@@ -1393,10 +1389,6 @@ bool CIRCNetwork::IsIRCConnected() const {
void CIRCNetwork::SetIRCSocket(CIRCSock* pIRCSock) { m_pIRCSock = 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 {
@@ -1405,9 +1397,6 @@ void CIRCNetwork::IRCConnected() {
}
void CIRCNetwork::IRCDisconnected() {
for (CClient* pClient : m_vClients) {
pClient->ClearServerDependentCaps();
}
m_pIRCSock = nullptr;
SetIRCServer("");
@@ -1417,6 +1406,17 @@ void CIRCNetwork::IRCDisconnected() {
CheckIRCConnect();
}
void CIRCNetwork::NotifyClientsAboutServerDependentCap(const CString& sCap, bool bValue) {
CString sValue = GetIRCSock() ? GetIRCSock()->GetCapLsValue(sCap) : "";
for (CClient* pClient : m_vClients) {
pClient->NotifyServerDependentCap(sCap, bValue, sValue);
}
}
bool CIRCNetwork::IsServerCapAccepted(const CString& sCap) const {
return m_pIRCSock && m_pIRCSock->IsCapAccepted(sCap);
}
void CIRCNetwork::SetIRCConnectEnabled(bool b) {
m_bIRCConnectEnabled = b;
+96 -57
View File
@@ -246,7 +246,9 @@ void CIRCSock::SendNextCap() {
if (!m_uCapPaused) {
if (m_ssPendingCaps.empty()) {
// We already got all needed ACK/NAK replies.
PutIRC("CAP END");
if (!m_bAuthed) {
PutIRC("CAP END");
}
} else {
CString sCap = *m_ssPendingCaps.begin();
m_ssPendingCaps.erase(m_ssPendingCaps.begin());
@@ -262,9 +264,9 @@ void CIRCSock::ResumeCap() {
SendNextCap();
}
bool CIRCSock::OnServerCapAvailable(const CString& sCap) {
bool CIRCSock::OnServerCapAvailable(const CString& sCap, const CString& sValue) {
bool bResult = false;
IRCSOCKMODULECALL(OnServerCapAvailable(sCap), &bResult);
IRCSOCKMODULECALL(OnServerCapAvailable(sCap, sValue), &bResult);
return bResult;
}
@@ -347,67 +349,94 @@ bool CIRCSock::OnAwayMessage(CMessage& Message) {
}
bool CIRCSock::OnCapabilityMessage(CMessage& Message) {
// CAPs are supported only before authorization.
if (!m_bAuthed) {
// The first parameter is most likely "*". No idea why, the
// CAP spec don't mention this, but all implementations
// I've seen add this extra asterisk
CString sSubCmd = Message.GetParam(1);
// The first parameter is most likely "*". No idea why, the
// CAP spec don't mention this, but all implementations
// I've seen add this extra asterisk
CString sSubCmd = Message.GetParam(1);
// If the caplist of a reply is too long, it's split
// into multiple replies. A "*" is prepended to show
// that the list was split into multiple replies.
// This is useful mainly for LS. For ACK and NAK
// replies, there's no real need for this, because
// we request only 1 capability per line.
// If we will need to support broken servers or will
// send several requests per line, need to delay ACK
// actions until all ACK lines are received and
// to recognize past request of NAK by 100 chars
// of this reply.
CString sArgs;
if (Message.GetParam(2) == "*") {
sArgs = Message.GetParam(3);
} else {
sArgs = Message.GetParam(2);
// If the caplist of a reply is too long, it's split
// into multiple replies. A "*" is prepended to show
// that the list was split into multiple replies.
// This is useful mainly for LS. For ACK and NAK
// replies, there's no real need for this, because
// we request only 1 capability per line.
// If we will need to support broken servers or will
// send several requests per line, need to delay ACK
// actions until all ACK lines are received and
// to recognize past request of NAK by 100 chars
// of this reply.
// As for LS, we shouldn't don't send END after receiving first line,
// because interesting caps can be on next line.
CString sArgs;
bool bSendNext = true;
if (Message.GetParam(2) == "*") {
bSendNext = false;
sArgs = Message.GetParam(3);
} else {
sArgs = Message.GetParam(2);
}
std::map<CString, std::function<void(bool bVal)>> mSupportedCaps = {
{"multi-prefix", [this](bool bVal) { m_bNamesx = bVal; }},
{"userhost-in-names", [this](bool bVal) { m_bUHNames = bVal; }},
{"cap-notify", [](bool bVal) {}},
{"server-time", [this](bool bVal) { m_bServerTime = bVal; }},
{"znc.in/server-time-iso",
[this](bool bVal) { m_bServerTime = bVal; }},
};
auto RemoveCap = [&](const CString& sCap) {
IRCSOCKMODULECALL(OnServerCapResult(sCap, false), NOTHING);
auto it = mSupportedCaps.find(sCap);
if (it != mSupportedCaps.end()) {
it->second(false);
}
m_ssAcceptedCaps.erase(sCap);
m_ssPendingCaps.erase(sCap);
};
std::map<CString, std::function<void(bool bVal)>> mSupportedCaps = {
{"multi-prefix", [this](bool bVal) { m_bNamesx = bVal; }},
{"userhost-in-names", [this](bool bVal) { m_bUHNames = bVal; }},
{"away-notify", [this](bool bVal) { m_bAwayNotify = bVal; }},
{"account-notify", [this](bool bVal) { m_bAccountNotify = bVal; }},
{"account-tag", [this](bool bVal) { m_bAccountTag = bVal; }},
{"extended-join", [this](bool bVal) { m_bExtendedJoin = bVal; }},
{"server-time", [this](bool bVal) { m_bServerTime = bVal; }},
{"znc.in/server-time-iso",
[this](bool bVal) { m_bServerTime = bVal; }},
};
if (sSubCmd == "LS" || sSubCmd == "NEW") {
VCString vsTokens;
sArgs.Split(" ", vsTokens, false);
if (sSubCmd == "LS") {
VCString vsTokens;
sArgs.Split(" ", vsTokens, false);
for (const CString& sCap : vsTokens) {
if (OnServerCapAvailable(sCap) || mSupportedCaps.count(sCap)) {
m_ssPendingCaps.insert(sCap);
}
for (const CString& sToken : vsTokens) {
CString sCap, sValue;
int eq = sToken.find('=');
if (eq == std::string::npos) {
sCap = sToken;
} else {
sCap = sToken.substr(0, eq);
sValue = sToken.substr(eq + 1);
}
} else if (sSubCmd == "ACK") {
sArgs.Trim();
IRCSOCKMODULECALL(OnServerCapResult(sArgs, true), NOTHING);
const auto& it = mSupportedCaps.find(sArgs);
if (it != mSupportedCaps.end()) {
it->second(true);
m_msCapLsValues[sCap] = sValue;
if (OnServerCapAvailable(sCap, sValue) || mSupportedCaps.count(sCap)) {
m_ssPendingCaps.insert(sCap);
}
m_ssAcceptedCaps.insert(sArgs);
} else if (sSubCmd == "NAK") {
// This should work because there's no [known]
// capability with length of name more than 100 characters.
sArgs.Trim();
IRCSOCKMODULECALL(OnServerCapResult(sArgs, false), NOTHING);
}
} else if (sSubCmd == "ACK") {
sArgs.Trim();
IRCSOCKMODULECALL(OnServerCapResult(sArgs, true), NOTHING);
auto it = mSupportedCaps.find(sArgs);
if (it != mSupportedCaps.end()) {
it->second(true);
}
m_ssAcceptedCaps.insert(sArgs);
} else if (sSubCmd == "NAK") {
// This should work because there's no [known]
// capability with length of name more than 100 characters.
sArgs.Trim();
RemoveCap(sArgs);
} else if (sSubCmd == "DEL") {
VCString vsTokens;
sArgs.Split(" ", vsTokens, false);
for (const CString& sCap : vsTokens) {
RemoveCap(sCap);
m_msCapLsValues.erase(sCap);
}
}
if (bSendNext) {
SendNextCap();
}
// Don't forward any CAP stuff to the client
@@ -1222,7 +1251,7 @@ void CIRCSock::Connected() {
&bReturn);
if (bReturn) return;
PutIRC("CAP LS");
PutIRC("CAP LS 302");
if (!sPass.empty()) {
PutIRC("PASS " + sPass);
@@ -1411,6 +1440,16 @@ CString CIRCSock::GetISupport(const CString& sKey,
}
}
CString CIRCSock::GetCapLsValue(const CString& sKey,
const CString& sDefault) const {
MCString::const_iterator i = m_msCapLsValues.find(sKey);
if (i == m_msCapLsValues.end()) {
return sDefault;
} else {
return i->second;
}
}
void CIRCSock::SendAltNick(const CString& sBadNick) {
const CString& sLastNick = m_Nick.GetNick();
+166 -12
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
#include <znc/IRCSock.h>
#include <znc/Modules.h>
#include <znc/FileUtils.h>
#include <znc/Template.h>
@@ -160,6 +161,31 @@ CModule::CModule(ModHandle pDLL, CUser* pUser, CIRCNetwork* pNetwork,
}
CModule::~CModule() {
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
// pCap->OnClientChangedSupport is useless (and even dangerous) to call
// from the destructor, since the derived CModule class is gone already.
// But still need to tell clients via cap-notify that the cap is gone.
switch (GetType()) {
case CModInfo::NetworkModule:
GetNetwork()->NotifyClientsAboutServerDependentCap(sName,
false);
break;
case CModInfo::UserModule:
for (CIRCNetwork* pNetwork : GetUser()->GetNetworks()) {
pNetwork->NotifyClientsAboutServerDependentCap(sName,
false);
}
break;
case CModInfo::GlobalModule:
for (auto& [_, pUser] : CZNC::Get().GetUserMap()) {
for (CIRCNetwork* pNetwork : pUser->GetNetworks()) {
pNetwork->NotifyClientsAboutServerDependentCap(sName,
false);
}
}
}
}
while (!m_sTimers.empty()) {
RemTimer(*m_sTimers.begin());
}
@@ -606,7 +632,24 @@ bool CModule::OnBoot() { return true; }
void CModule::OnPreRehash() {}
void CModule::OnPostRehash() {}
void CModule::OnIRCDisconnected() {}
void CModule::InternalServerDependentCapsOnIRCDisconnected() {
OnIRCDisconnected();
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
GetNetwork()->NotifyClientsAboutServerDependentCap(sName, false);
for (CClient* pClient : GetNetwork()->GetClients()) {
pCap->OnClientChangedSupport(pClient, false);
}
}
}
void CModule::OnIRCConnected() {}
void CModule::InternalServerDependentCapsOnIRCConnected() {
OnIRCConnected();
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
if (GetNetwork()->IsServerCapAccepted(sName)) {
GetNetwork()->NotifyClientsAboutServerDependentCap(sName, true);
}
}
}
CModule::EModRet CModule::OnIRCConnecting(CIRCSock* IRCSock) {
return CONTINUE;
}
@@ -997,9 +1040,59 @@ CModule::EModRet CModule::OnSendToIRC(CString& sLine) { return CONTINUE; }
CModule::EModRet CModule::OnSendToIRCMessage(CMessage& Message) {
return CONTINUE;
}
void CModule::OnClientAttached() {}
void CModule::InternalServerDependentCapsOnClientAttached() {
OnClientAttached();
if (!GetNetwork()) return;
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
if (GetNetwork()->IsServerCapAccepted(sName)) {
GetClient()->NotifyServerDependentCap(sName, true, GetNetwork()->GetIRCSock()->GetCapLsValue(sName));
}
}
}
void CModule::OnClientDetached() {}
void CModule::InternalServerDependentCapsOnClientDetached() {
OnClientDetached();
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
GetClient()->NotifyServerDependentCap(sName, false, "");
pCap->OnClientChangedSupport(GetClient(), false);
}
}
bool CModule::OnServerCapAvailable(const CString& sCap) { return false; }
bool CModule::OnServerCap302Available(const CString& sCap,
const CString& sValue) {
return OnServerCapAvailable(sCap);
}
bool CModule::InternalServerDependentCapsOnServerCap302Available(const CString& sCap,
const CString& sValue) {
auto it = m_mServerDependentCaps.find(sCap);
if (it == m_mServerDependentCaps.end())
return OnServerCap302Available(sCap, sValue);
if (GetNetwork()->IsServerCapAccepted(sCap)) {
// This can happen when server sent CAP NEW with another value.
GetNetwork()->NotifyClientsAboutServerDependentCap(sCap, true);
// It's enabled already, no need to REQ it again.
return false;
}
return true;
}
void CModule::OnServerCapResult(const CString& sCap, bool bSuccess) {}
void CModule::InternalServerDependentCapsOnServerCapResult(const CString& sCap,
bool bSuccess) {
OnServerCapResult(sCap, bSuccess);
auto it = m_mServerDependentCaps.find(sCap);
if (it == m_mServerDependentCaps.end()) return;
it->second->OnServerChangedSupport(GetNetwork(), bSuccess);
if (GetNetwork()->GetIRCSock()->IsAuthed()) {
GetNetwork()->NotifyClientsAboutServerDependentCap(sCap, bSuccess);
if (!bSuccess) {
for (CClient* pClient : GetNetwork()->GetClients()) {
it->second->OnClientChangedSupport(pClient, false);
}
}
}
}
bool CModule::PutIRC(const CString& sLine) {
return m_pNetwork ? m_pNetwork->PutIRC(sLine) : false;
@@ -1069,12 +1162,44 @@ CModule::EModRet CModule::OnUnknownUserRawMessage(CMessage& Message) {
return CONTINUE;
}
void CModule::OnClientCapLs(CClient* pClient, SCString& ssCaps) {}
void CModule::InternalServerDependentCapsOnClientCapLs(CClient* pClient, SCString& ssCaps) {
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
if (GetNetwork() && GetNetwork()->IsServerCapAccepted(sName)) {
if (pClient->HasCap302()) {
CString sValue =
GetNetwork()->GetIRCSock()->GetCapLsValue(sName);
if (!sValue.empty()) {
ssCaps.insert(sName + '=' + sValue);
} else {
ssCaps.insert(sName);
}
} else {
ssCaps.insert(sName);
}
}
}
OnClientCapLs(pClient, ssCaps);
}
bool CModule::IsClientCapSupported(CClient* pClient, const CString& sCap,
bool bState) {
return false;
bool bState) { return false; }
bool CModule::InternalServerDependentCapsIsClientCapSupported(
CClient* pClient, const CString& sCap, bool bState) {
auto it = m_mServerDependentCaps.find(sCap);
if (it == m_mServerDependentCaps.end())
return IsClientCapSupported(pClient, sCap, bState);
if (!bState) return true;
return GetNetwork() && GetNetwork()->IsServerCapAccepted(sCap);
}
void CModule::OnClientCapRequest(CClient* pClient, const CString& sCap,
bool bState) {}
void CModule::InternalServerDependentCapsOnClientCapRequest(CClient* pClient,
const CString& sCap,
bool bState) {
OnClientCapRequest(pClient, sCap, bState);
auto it = m_mServerDependentCaps.find(sCap);
if (it == m_mServerDependentCaps.end()) return;
it->second->OnClientChangedSupport(pClient, bState);
}
CModule::EModRet CModule::OnModuleLoading(const CString& sModName,
const CString& sArgs,
CModInfo::EModuleType eType,
@@ -1092,6 +1217,11 @@ CModule::EModRet CModule::OnGetModInfo(CModInfo& ModInfo,
}
void CModule::OnGetAvailableMods(set<CModInfo>& ssMods,
CModInfo::EModuleType eType) {}
void CModule::AddServerDependentCapability(const CString& sName,
std::unique_ptr<CCapability> pCap) {
pCap->SetModule(this);
m_mServerDependentCaps[sName] = std::move(pCap);
}
CModules::CModules()
: m_pUser(nullptr), m_pNetwork(nullptr), m_pClient(nullptr) {}
@@ -1131,7 +1261,7 @@ bool CModules::OnPostRehash() {
return false;
}
bool CModules::OnIRCConnected() {
MODUNLOADCHK(OnIRCConnected());
MODUNLOADCHK(InternalServerDependentCapsOnIRCConnected());
return false;
}
bool CModules::OnIRCConnecting(CIRCSock* pIRCSock) {
@@ -1149,7 +1279,7 @@ bool CModules::OnBroadcast(CString& sMessage) {
MODHALTCHK(OnBroadcast(sMessage));
}
bool CModules::OnIRCDisconnected() {
MODUNLOADCHK(OnIRCDisconnected());
MODUNLOADCHK(InternalServerDependentCapsOnIRCDisconnected());
return false;
}
@@ -1489,9 +1619,17 @@ bool CModules::OnModCTCP(const CString& sMessage) {
MODUNLOADCHK(OnModCTCP(sMessage));
return false;
}
bool CModules::OnClientAttached() {
MODUNLOADCHK(InternalServerDependentCapsOnClientAttached());
return false;
}
bool CModules::OnClientDetached() {
MODUNLOADCHK(InternalServerDependentCapsOnClientDetached());
return false;
}
// Why MODHALTCHK works only with functions returning EModRet ? :(
bool CModules::OnServerCapAvailable(const CString& sCap) {
bool CModules::OnServerCapAvailable(const CString& sCap, const CString& sValue) {
bool bResult = false;
for (CModule* pMod : *this) {
try {
@@ -1499,12 +1637,19 @@ bool CModules::OnServerCapAvailable(const CString& sCap) {
pMod->SetClient(m_pClient);
if (m_pUser) {
CUser* pOldUser = pMod->GetUser();
CIRCNetwork* pOldNetwork = pMod->GetNetwork();
pMod->SetUser(m_pUser);
bResult |= pMod->OnServerCapAvailable(sCap);
pMod->SetNetwork(m_pNetwork);
bResult |=
pMod->InternalServerDependentCapsOnServerCap302Available(
sCap, sValue);
pMod->SetUser(pOldUser);
pMod->SetNetwork(pOldNetwork);
} else {
// WTF? Is that possible?
bResult |= pMod->OnServerCapAvailable(sCap);
bResult |=
pMod->InternalServerDependentCapsOnServerCap302Available(
sCap, sValue);
}
pMod->SetClient(pOldClient);
} catch (const CModule::EModException& e) {
@@ -1517,7 +1662,7 @@ bool CModules::OnServerCapAvailable(const CString& sCap) {
}
bool CModules::OnServerCapResult(const CString& sCap, bool bSuccess) {
MODUNLOADCHK(OnServerCapResult(sCap, bSuccess));
MODUNLOADCHK(InternalServerDependentCapsOnServerCapResult(sCap, bSuccess));
return false;
}
@@ -1555,7 +1700,7 @@ bool CModules::OnUnknownUserRawMessage(CMessage& Message) {
}
bool CModules::OnClientCapLs(CClient* pClient, SCString& ssCaps) {
MODUNLOADCHK(OnClientCapLs(pClient, ssCaps));
MODUNLOADCHK(InternalServerDependentCapsOnClientCapLs(pClient, ssCaps));
return false;
}
@@ -1569,12 +1714,19 @@ bool CModules::IsClientCapSupported(CClient* pClient, const CString& sCap,
pMod->SetClient(m_pClient);
if (m_pUser) {
CUser* pOldUser = pMod->GetUser();
CIRCNetwork* pOldNetwork = pMod->GetNetwork();
pMod->SetUser(m_pUser);
bResult |= pMod->IsClientCapSupported(pClient, sCap, bState);
pMod->SetNetwork(m_pNetwork);
bResult |=
pMod->InternalServerDependentCapsIsClientCapSupported(
pClient, sCap, bState);
pMod->SetUser(pOldUser);
pMod->SetNetwork(pOldNetwork);
} else {
// WTF? Is that possible?
bResult |= pMod->IsClientCapSupported(pClient, sCap, bState);
bResult |=
pMod->InternalServerDependentCapsIsClientCapSupported(
pClient, sCap, bState);
}
pMod->SetClient(pOldClient);
} catch (const CModule::EModException& e) {
@@ -1588,7 +1740,8 @@ bool CModules::IsClientCapSupported(CClient* pClient, const CString& sCap,
bool CModules::OnClientCapRequest(CClient* pClient, const CString& sCap,
bool bState) {
MODUNLOADCHK(OnClientCapRequest(pClient, sCap, bState));
MODUNLOADCHK(
InternalServerDependentCapsOnClientCapRequest(pClient, sCap, bState));
return false;
}
@@ -1876,6 +2029,7 @@ void CModules::GetDefaultMods(set<CModInfo>& ssMods,
const map<CString, CModInfo::EModuleType> ns = {
{"chansaver", CModInfo::UserModule},
{"controlpanel", CModInfo::UserModule},
{"corecaps", CModInfo::GlobalModule},
{"simple_away", CModInfo::NetworkModule},
{"webadmin", CModInfo::GlobalModule}};
-16
View File
@@ -172,22 +172,6 @@ TEST_F(ClientTest, UserhostInNames) { // aka UHNAMES
ElementsAre(msg.ToString(), extmsg.ToString()));
}
TEST_F(ClientTest, ExtendedJoin) {
m_pTestSock->ReadLine(":server CAP * ACK :extended-join");
m_pTestClient->Reset();
CMessage msg(":nick!user@host JOIN #channel");
CMessage extmsg(":nick!user@host JOIN #channel account :Real Name");
EXPECT_FALSE(m_pTestClient->HasExtendedJoin());
m_pTestClient->PutClient(extmsg);
EXPECT_THAT(m_pTestClient->vsLines, ElementsAre(msg.ToString()));
m_pTestClient->SetExtendedJoin(true);
EXPECT_TRUE(m_pTestClient->HasExtendedJoin());
m_pTestClient->PutClient(extmsg);
EXPECT_THAT(m_pTestClient->vsLines,
ElementsAre(msg.ToString(), extmsg.ToString()));
}
TEST_F(ClientTest, StatusMsg) {
m_pTestSock->ReadLine(
":irc.znc.in 001 me :Welcome to the Internet Relay Network me");
+8
View File
@@ -48,6 +48,14 @@ void WriteConfig(QString path) {
p.ReadUntil("Launch ZNC now?"); p.Write("no");
p.ShouldFinishItself();
// clang-format on
// Default 30s is too slow for the test
QFile conf(path + "/configs/znc.conf");
ASSERT_TRUE(conf.open(QIODevice::Append | QIODevice::Text));
QTextStream out(&conf);
out << R"(
ServerThrottle = 5
)";
}
void ZNCTest::SetUp() {
+258
View File
@@ -166,6 +166,7 @@ TEST_F(ZNCTest, InvalidConfigInChan) {
auto znc = Run();
znc->ShouldFinishItself(1);
}
TEST_F(ZNCTest, Encoding) {
auto znc = Run();
auto ircd = ConnectIRCd();
@@ -270,6 +271,55 @@ TEST_F(ZNCTest, AwayNotify) {
client.Write("znc shutdown");
}
TEST_F(ZNCTest, ExtendedJoin) {
auto znc = Run();
auto ircd = ConnectIRCd();
auto client = LoginClient();
ircd.Write(":server 001 user :welcome");
client.ReadUntil(" 001 ");
ircd.Write(":nick!user@host JOIN #channel account :Real Name");
// Not sure why it is like this when server sends such format unexpectedly.
client.ReadUntil("JOIN #channel account :Real Name");
ircd.Write("CAP nick ACK extended-join");
ircd.Write(":nick!user@host JOIN #channel2 account :Real Name");
QByteArray line;
client.ReadUntilAndGet("JOIN", line);
EXPECT_EQ(line.toStdString(), "JOIN #channel2");
client.Write("CAP REQ extended-join");
client.ReadUntil("CAP user ACK :extended-join");
ircd.Write(":nick!user@host JOIN #channel3 account :Real Name");
client.ReadUntil("JOIN #channel3 account :Real Name");
}
TEST_F(ZNCTest, CAP302LSWaitFull) {
auto znc = Run();
auto ircd = ConnectIRCd();
ircd.ReadUntil("CAP LS 302");
ircd.Write("CAP user LS * :away-notify");
ASSERT_THAT(ircd.ReadRemainder().toStdString(), Not(HasSubstr("away-notify")));
ircd.Write("CAP user LS :blahblah");
ircd.ReadUntil("CAP REQ :away-notify");
}
TEST_F(ZNCTest, CAP302NewDel) {
auto znc = Run();
auto ircd = ConnectIRCd();
auto client = LoginClient();
ircd.Write("CAP nick LS :blahblah");
ircd.ReadUntil("CAP END");
ircd.Write(":server 001 nick :Hello");
client.Write("CAP REQ :away-notify");
client.ReadUntil("NAK :away-notify");
client.Write("CAP REQ :cap-notify");
client.ReadUntil("ACK :cap-notify");
ircd.Write("CAP nick NEW :away-notify");
ircd.ReadUntil("CAP REQ :away-notify");
ircd.Write("CAP nick ACK :away-notify");
client.ReadUntil("CAP nick NEW :away-notify");
ircd.Write("CAP nick DEL :away-notify");
client.ReadUntil("CAP nick DEL :away-notify");
}
TEST_F(ZNCTest, JoinKey) {
QFile conf(m_dir.path() + "/configs/znc.conf");
ASSERT_TRUE(conf.open(QIODevice::Append | QIODevice::Text));
@@ -431,6 +481,214 @@ 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 <znc/Client.h>
#include <znc/Modules.h>
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();
int 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);
}
TEST_F(ZNCTest, CAP302LSValue) {
auto znc = Run();
auto ircd = ConnectIRCd();
auto client = LoginClient();
InstallModule("testmod.cpp", R"(
#include <znc/Client.h>
#include <znc/Modules.h>
class TestModule : public CModule {
public:
MODCONSTRUCTOR(TestModule) {}
void OnClientCapLs(CClient* pClient, SCString& ssCaps) override {
if (pClient->HasCap302()) {
ssCaps.insert("testcap=blah");
} else {
ssCaps.insert("testcap");
}
}
};
GLOBALMODULEDEFS(TestModule, "Test")
)");
client.Write("znc loadmod testmod");
client.ReadUntil("Loaded module testmod");
auto client2 = ConnectClient();
client2.Write("CAP LS");
client2.ReadUntil("testcap ");
client2 = ConnectClient();
client2.Write("CAP LS 302");
client2.ReadUntil("testcap=");
}
class AllLanguages : public ZNCTest, public testing::WithParamInterface<int> {};
INSTANTIATE_TEST_CASE_P(LanguagesTests, AllLanguages, testing::Values(1, 2, 3));
TEST_P(AllLanguages, ServerDependentCapInModule) {
auto znc = Run();
auto ircd = ConnectIRCd();
auto client = LoginClient();
switch (GetParam()) {
case 1:
InstallModule("testmod.cpp", R"(
#include <znc/Modules.h>
class TestModule : public CModule {
class TestCap : public CCapability {
void OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) override {
GetModule()->PutModule("Server changed support: " + CString(bState));
}
void OnClientChangedSupport(CClient* pClient, bool bState) override {
GetModule()->PutModule("Client changed support: " + CString(bState));
}
};
public:
MODCONSTRUCTOR(TestModule) {
AddServerDependentCapability("testcap", std::make_unique<TestCap>());
}
};
MODULEDEFS(TestModule, "Test")
)");
break;
case 2:
if (QProcessEnvironment::systemEnvironment().value(
"DISABLED_ZNC_PERL_PYTHON_TEST") == "1") {
return;
}
znc->CanLeak();
InstallModule("testmod.py", R"(
import znc
class testmod(znc.Module):
def OnLoad(self, args, ret):
def server_change(net, state):
self.PutModule('Server changed support: ' + ('true' if state else 'false'))
def client_change(client, state):
self.PutModule('Client changed support: ' + ('true' if state else 'false'))
self.AddServerDependentCapability('testcap', server_change, client_change)
return True
)");
client.Write("znc loadmod modpython");
break;
case 3:
if (QProcessEnvironment::systemEnvironment().value(
"DISABLED_ZNC_PERL_PYTHON_TEST") == "1") {
return;
}
znc->CanLeak();
InstallModule("testmod.pm", R"(
package testmod;
use base 'ZNC::Module';
sub OnLoad {
my $self = shift;
my $listen = $self->AddServerDependentCapability('testcap', sub {
my ($net, $state) = @_;
$self->PutModule('Server changed support: ' . ($state ? 'true' : 'false'));
}, sub {
my ($client, $state) = @_;
$self->PutModule('Client changed support: ' . ($state ? 'true' : 'false'));
});
return 1;
}
1;
)");
client.Write("znc loadmod modperl");
break;
}
client.Write("znc loadmod testmod");
client.ReadUntil("Loaded module testmod");
client.Write("znc addnetwork net2");
client.Close();
client = ConnectClient();
client.Write("CAP LS 302");
client.Write("PASS :hunter2");
client.Write("NICK nick");
client.Write("USER user/test x x :x");
client.Write("CAP END");
client.ReadUntil("Welcome");
ircd.Write("001 nick Welcome");
ircd.Write("CAP nick NEW testcap=value");
ircd.ReadUntil("CAP REQ :testcap");
ircd.Write("CAP nick ACK :testcap");
client.ReadUntil(":Server changed support: true");
client.ReadUntil("CAP nick NEW :testcap=value");
client.Write("CAP REQ testcap");
client.ReadUntil(":Client changed support: true");
client.ReadUntil("CAP nick ACK :testcap");
client.Write("CAP LS");
client.ReadUntil(" testcap=value ");
ircd.Write("CAP nick DEL testcap");
client.ReadUntil(":Server changed support: false");
client.ReadUntil("CAP nick DEL :testcap");
client.ReadUntil(":Client changed support: false");
ircd.Close();
// TODO combine multiple DELs to single line
client.ReadUntil("CAP nick DEL :testcap");
client.ReadUntil(":Client changed support: false");
ircd = ConnectIRCd();
ircd.ReadUntil("CAP LS 302");
ircd.Write("CAP nick LS :testcap=new");
ircd.ReadUntil("CAP REQ :testcap");
ircd.Write("CAP nick ACK :testcap");
client.ReadUntil(":Server changed support: true");
ircd.ReadUntil("CAP END");
// NEW waits until 001
ASSERT_THAT(ircd.ReadRemainder().toStdString(), Not(HasSubstr("testcap")));
ircd.Write("001 nick Welcome");
// TODO combine multiple NEWs to single line
client.ReadUntil("CAP nick NEW :testcap=new");
client.ReadUntil("Welcome");
// NEW with new value without DEL
ircd.Write("CAP nick NEW testcap=another");
client.ReadUntil("CAP nick NEW :testcap=another");
client.Write("znc jumpnetwork net2");
client.ReadUntil("CAP nick DEL :testcap");
client.ReadUntil(":Client changed support: false");
client.Write("znc jumpnetwork test");
client.ReadUntil("CAP nick NEW :testcap=another");
}
TEST_F(ZNCTest, HashUpgrade) {
QFile conf(m_dir.path() + "/configs/znc.conf");
ASSERT_TRUE(conf.open(QIODevice::Append | QIODevice::Text));