mirror of
https://github.com/znc/znc.git
synced 2026-07-28 12:33:33 +02:00
Merge pull request #2030 from RealKindOne/fix-client-cycle
Fix join/part not working when a client sends '/cycle' or '/hop'.
This commit is contained in:
@@ -168,6 +168,7 @@ class CChan : private CCoreTranslationMixin {
|
||||
void Enable();
|
||||
void IncJoinTries() { m_uJoinTries++; }
|
||||
void ResetJoinTries() { m_uJoinTries = 0; }
|
||||
void SetParting(bool b) { m_bParting = b; }
|
||||
// !Setters
|
||||
|
||||
// Getters
|
||||
@@ -199,6 +200,7 @@ class CChan : private CCoreTranslationMixin {
|
||||
bool HasAutoClearChanBufferSet() const {
|
||||
return m_bHasAutoClearChanBufferSet;
|
||||
}
|
||||
bool IsParting() const { return m_bParting; }
|
||||
// !Getters
|
||||
private:
|
||||
protected:
|
||||
@@ -223,6 +225,7 @@ class CChan : private CCoreTranslationMixin {
|
||||
CBuffer m_Buffer;
|
||||
|
||||
bool m_bModeKnown;
|
||||
bool m_bParting;
|
||||
std::map<char, CString> m_mcsModes;
|
||||
};
|
||||
|
||||
|
||||
+20
-9
@@ -30,6 +30,7 @@ CChan::CChan(const CString& sName, CIRCNetwork* pNetwork, bool bInConfig,
|
||||
CConfig* pConfig)
|
||||
: m_bDetached(false),
|
||||
m_bIsOn(false),
|
||||
m_bParting(false),
|
||||
m_bAutoClearChanBuffer(pNetwork->GetUser()->AutoClearChanBuffer()),
|
||||
m_bInConfig(bInConfig),
|
||||
m_bDisabled(false),
|
||||
@@ -84,6 +85,7 @@ CChan::~CChan() { ClearNicks(); }
|
||||
|
||||
void CChan::Reset() {
|
||||
m_bIsOn = false;
|
||||
m_bParting = false;
|
||||
m_bModeKnown = false;
|
||||
m_mcsModes.clear();
|
||||
m_sTopic = "";
|
||||
@@ -143,27 +145,36 @@ void CChan::JoinUser(const CString& sKey) {
|
||||
if (!IsOn() && !sKey.empty()) {
|
||||
SetKey(sKey);
|
||||
}
|
||||
if (m_pNetwork->IsIRCConnected() && !IsOn()) {
|
||||
if (m_pNetwork->IsIRCConnected() && (!IsOn() || IsParting())) {
|
||||
m_pNetwork->PutIRC("JOIN " + GetName() + " " + GetKey());
|
||||
}
|
||||
}
|
||||
|
||||
void CChan::AttachUser(CClient* pClient) {
|
||||
|
||||
// Sending '/znc attach #channel' applies for all clients connected.
|
||||
// Sending '/join #channel' only applies for the client that sent
|
||||
// the command. This causes a de-sync with IRC events like QUIT
|
||||
// displaying in the other clients server/status window.
|
||||
|
||||
// Make '/join #channel' work the same as '/znc attach #channel'
|
||||
CClient* pTarget = IsDetached() ? nullptr : pClient;
|
||||
|
||||
m_pNetwork->PutUser(
|
||||
":" + m_pNetwork->GetIRCNick().GetNickMask() + " JOIN :" + GetName(),
|
||||
pClient);
|
||||
pTarget);
|
||||
|
||||
if (!GetTopic().empty()) {
|
||||
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 332 " +
|
||||
m_pNetwork->GetIRCNick().GetNick() + " " +
|
||||
GetName() + " :" + GetTopic(),
|
||||
pClient);
|
||||
pTarget);
|
||||
if (!GetTopicOwner().empty()) {
|
||||
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 333 " +
|
||||
m_pNetwork->GetIRCNick().GetNick() + " " +
|
||||
GetName() + " " + GetTopicOwner() + " " +
|
||||
CString(GetTopicDate()),
|
||||
pClient);
|
||||
pTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,10 +187,10 @@ void CChan::AttachUser(CClient* pClient) {
|
||||
const vector<CClient*>& vpClients = m_pNetwork->GetClients();
|
||||
for (CClient* pEachClient : vpClients) {
|
||||
CClient* pThisClient;
|
||||
if (!pClient)
|
||||
if (!pTarget)
|
||||
pThisClient = pEachClient;
|
||||
else
|
||||
pThisClient = pClient;
|
||||
pThisClient = pTarget;
|
||||
|
||||
for (map<CString, CNick>::iterator a = m_msNicks.begin();
|
||||
a != m_msNicks.end(); ++a) {
|
||||
@@ -210,18 +221,18 @@ void CChan::AttachUser(CClient* pClient) {
|
||||
}
|
||||
}
|
||||
|
||||
if (pClient) // We only want to do this for one client
|
||||
if (pTarget)
|
||||
break;
|
||||
}
|
||||
|
||||
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 366 " +
|
||||
m_pNetwork->GetIRCNick().GetNick() + " " +
|
||||
GetName() + " :End of /NAMES list.",
|
||||
pClient);
|
||||
pTarget);
|
||||
m_bDetached = false;
|
||||
|
||||
// Send Buffer
|
||||
SendBuffer(pClient);
|
||||
SendBuffer(pTarget);
|
||||
}
|
||||
|
||||
void CChan::DetachUser() {
|
||||
|
||||
+2
-1
@@ -1469,7 +1469,7 @@ bool CClient::OnJoinMessage(CJoinMessage& Message) {
|
||||
if (pChan) {
|
||||
if (pChan->IsDetached())
|
||||
pChan->AttachUser(this);
|
||||
else
|
||||
else if (!pChan->IsOn() || pChan->IsParting())
|
||||
pChan->JoinUser(sKey);
|
||||
continue;
|
||||
} else if (!sChannel.empty()) {
|
||||
@@ -1592,6 +1592,7 @@ bool CClient::OnPartMessage(CPartMessage& Message) {
|
||||
PutStatusNotice(t_f("Removing channel {1}")(sChan));
|
||||
m_pNetwork->DelChan(sChan);
|
||||
} else {
|
||||
if (pChan) pChan->SetParting(true);
|
||||
sChans += (sChans.empty()) ? sChan : CString("," + sChan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,6 +692,7 @@ bool CIRCSock::OnJoinMessage(CJoinMessage& Message) {
|
||||
if (pChan) {
|
||||
pChan->Enable();
|
||||
pChan->SetIsOn(true);
|
||||
pChan->SetParting(false);
|
||||
PutIRC("MODE " + sChan);
|
||||
}
|
||||
} else {
|
||||
@@ -727,6 +728,7 @@ bool CIRCSock::OnKickMessage(CKickMessage& Message) {
|
||||
|
||||
if (GetNick().Equals(sKickedNick) && pChan) {
|
||||
pChan->SetIsOn(false);
|
||||
pChan->SetParting(false);
|
||||
|
||||
// Don't try to rejoin!
|
||||
pChan->Disable();
|
||||
@@ -1119,6 +1121,19 @@ bool CIRCSock::OnNumericMessage(CNumericMessage& Message) {
|
||||
m_pNetwork->AddMotdBuffer(BufferMessage(Message));
|
||||
}
|
||||
break;
|
||||
case 403: // ERR_NOSUCHCHANNEL
|
||||
case 442: // ERR_NOTONCHANNEL
|
||||
{
|
||||
CString sChan = Message.GetParam(1);
|
||||
CChan* pChan = m_pNetwork->FindChan(sChan);
|
||||
if (pChan && pChan->IsParting()) {
|
||||
pChan->SetIsOn(false);
|
||||
pChan->SetParting(false);
|
||||
m_pNetwork->PutStatus(
|
||||
t_f("PART failed for channel {1}")(sChan));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 437:
|
||||
// :irc.server.net 437 * badnick :Nick/channel is temporarily unavailable
|
||||
// :irc.server.net 437 mynick badnick :Nick/channel is temporarily unavailable
|
||||
|
||||
@@ -558,3 +558,41 @@ TEST_F(IRCSockTest, ChanMode) {
|
||||
":are supported by this server");
|
||||
m_pTestSock->ReadLine(":irc.znc.in 324 me #chan +ntf ");
|
||||
}
|
||||
|
||||
TEST_F(IRCSockTest, PartingFlag) {
|
||||
// Set channel as joined
|
||||
m_pTestChan->SetIsOn(true);
|
||||
|
||||
CMessage clientPart("PART #chan");
|
||||
m_pTestClient->ReadLine(clientPart.ToString());
|
||||
|
||||
EXPECT_THAT(m_pTestSock->vsLines, ElementsAre("PART #chan"));
|
||||
EXPECT_TRUE(m_pTestChan->IsParting());
|
||||
|
||||
m_pTestSock->Reset();
|
||||
|
||||
CMessage serverPart(":me PART #chan");
|
||||
m_pTestSock->ReadLine(serverPart.ToString());
|
||||
|
||||
// Verify channel was deleted
|
||||
EXPECT_EQ(m_pTestNetwork->FindChan("#chan"), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(IRCSockTest, PartingFlagOnError) {
|
||||
// Set channel as joined
|
||||
m_pTestChan->SetIsOn(true);
|
||||
|
||||
CMessage clientPart("PART #chan");
|
||||
m_pTestClient->ReadLine(clientPart.ToString());
|
||||
|
||||
m_pTestSock->Reset();
|
||||
|
||||
CMessage errorMsg(":server 442 me #chan :You're not on that channel");
|
||||
m_pTestSock->ReadLine(errorMsg.ToString());
|
||||
|
||||
// IsParting() should be cleared
|
||||
EXPECT_FALSE(m_pTestChan->IsParting());
|
||||
|
||||
// Verify channel was deleted
|
||||
EXPECT_NE(m_pTestNetwork->FindChan("#chan"), nullptr);
|
||||
}
|
||||
@@ -1278,5 +1278,151 @@ TEST_F(ZNCTest, CAPDetached) {
|
||||
<< "Client saw chghost even though all channels are detached";
|
||||
}
|
||||
|
||||
// Test for CChan::AttachUser()
|
||||
TEST_F(ZNCTest, JoinDetachedChannelMultiClient) {
|
||||
auto znc = Run();
|
||||
auto ircd = ConnectIRCd();
|
||||
|
||||
auto client1 = LoginClient();
|
||||
ircd.Write(":server 001 nick :Hello");
|
||||
client1.Write("JOIN #test");
|
||||
ircd.ReadUntil("JOIN #test");
|
||||
ircd.Write(":nick JOIN :#test");
|
||||
ircd.Write(":server 353 nick #test :nick");
|
||||
ircd.Write(":server 366 nick #test :End of /NAMES list");
|
||||
client1.ReadUntil("End of /NAMES");
|
||||
|
||||
client1.Write("DETACH #test");
|
||||
client1.ReadUntil("Detached 1 channel");
|
||||
|
||||
auto client2 = LoginClient();
|
||||
client2.ReadUntil("001");
|
||||
|
||||
// Both client1 and client2 should be in #test
|
||||
client2.Write("JOIN #test");
|
||||
|
||||
client1.ReadUntil(":nick JOIN :#test");
|
||||
client2.ReadUntil(":nick JOIN :#test");
|
||||
|
||||
client1.ReadUntil("353");
|
||||
client2.ReadUntil("353");
|
||||
|
||||
ircd.Write(":other!user@host JOIN :#test");
|
||||
client1.ReadUntil(":other!user@host JOIN :#test");
|
||||
client2.ReadUntil(":other!user@host JOIN :#test");
|
||||
|
||||
ircd.Write(":other!user@host QUIT :quit message");
|
||||
client1.ReadUntil(":other!user@host QUIT :quit message");
|
||||
client2.ReadUntil(":other!user@host QUIT :quit message");
|
||||
}
|
||||
|
||||
// Commit ad7bd6d7eed84648638e1b6fd69546b9fe496576
|
||||
// prevented rejoining when a client cycles a channel.
|
||||
TEST_F(ZNCTest, ClientCycleChannel) {
|
||||
auto znc = Run();
|
||||
auto ircd = ConnectIRCd();
|
||||
auto client = LoginClient();
|
||||
|
||||
ircd.Write(":server 001 nick :Hello");
|
||||
client.Write("JOIN #test");
|
||||
ircd.ReadUntil("JOIN #test");
|
||||
|
||||
ircd.Write(":nick JOIN :#test");
|
||||
ircd.Write(":server 353 nick #test :nick");
|
||||
ircd.Write(":server 366 nick #test :End of /NAMES list");
|
||||
client.ReadUntil("End of /NAMES");
|
||||
|
||||
// Clients have '/hop' or '/cycle' command that sends
|
||||
// 'PART #channel' and 'JOIN #channel'. Verify ZNC rejoins.
|
||||
client.Write(":nick PART #test");
|
||||
|
||||
// Verify PART is forwarded to server
|
||||
QByteArray partMsg;
|
||||
ircd.ReadUntilAndGet("PART", partMsg);
|
||||
EXPECT_THAT(partMsg.toStdString(), HasSubstr("PART #test"));
|
||||
|
||||
ircd.Write(":nick PART #test");
|
||||
client.ReadUntil(":nick PART #test");
|
||||
|
||||
client.Write(":nick JOIN #test");
|
||||
ircd.ReadUntil("JOIN #test");
|
||||
}
|
||||
|
||||
TEST_F(ZNCTest, PartWithError403) {
|
||||
auto znc = Run();
|
||||
auto ircd = ConnectIRCd();
|
||||
auto client = LoginClient();
|
||||
|
||||
// Join a channel first
|
||||
client.Write("JOIN #test");
|
||||
client.Close();
|
||||
|
||||
ircd.Write(":server 001 nick :Hello");
|
||||
ircd.ReadUntil("JOIN #test");
|
||||
ircd.Write(":nick JOIN :#test");
|
||||
ircd.Write(":server 353 nick #test :nick");
|
||||
ircd.Write(":server 366 nick #test :End of /NAMES list");
|
||||
|
||||
// Reconnect client and send PART
|
||||
client = LoginClient();
|
||||
client.ReadUntil(":nick JOIN :#test");
|
||||
client.Write("PART #test");
|
||||
|
||||
ircd.ReadUntil("PART #test");
|
||||
|
||||
// Server returns 403 error (ERR_NOSUCHCHANNEL) instead of confirming
|
||||
ircd.Write(":server 403 nick #test :No such channel");
|
||||
|
||||
// Verify client receives the status message about PART failure
|
||||
client.ReadUntil("PART failed for channel #test");
|
||||
|
||||
// Verify channel still exists by trying to rejoin
|
||||
client.Write("JOIN #test");
|
||||
ircd.ReadUntil("JOIN #test");
|
||||
ircd.Write(":nick JOIN :#test");
|
||||
ircd.Write(":server 353 nick #test :nick");
|
||||
ircd.Write(":server 366 nick #test :End of /NAMES list");
|
||||
client.ReadUntil(":nick JOIN :#test");
|
||||
}
|
||||
|
||||
TEST_F(ZNCTest, PartWithError442) {
|
||||
auto znc = Run();
|
||||
auto ircd = ConnectIRCd();
|
||||
auto client = LoginClient();
|
||||
|
||||
// Join a channel first
|
||||
client.Write("JOIN #test");
|
||||
client.Close();
|
||||
|
||||
ircd.Write(":server 001 nick :Hello");
|
||||
ircd.ReadUntil("JOIN #test");
|
||||
ircd.Write(":nick JOIN :#test");
|
||||
ircd.Write(":server 353 nick #test :nick");
|
||||
ircd.Write(":server 366 nick #test :End of /NAMES list");
|
||||
|
||||
// Reconnect client and send PART
|
||||
client = LoginClient();
|
||||
client.ReadUntil(":nick JOIN :#test");
|
||||
client.Write("PART #test :leaving");
|
||||
|
||||
QByteArray partMsg;
|
||||
ircd.ReadUntilAndGet("PART", partMsg);
|
||||
EXPECT_THAT(partMsg.toStdString(), HasSubstr("PART #test"));
|
||||
|
||||
// Server returns 442 error (ERR_NOTONCHANNEL) instead of confirming
|
||||
ircd.Write(":server 442 nick #test :You're not on that channel");
|
||||
|
||||
// Verify client receives the status message about PART failure
|
||||
client.ReadUntil("PART failed for channel #test");
|
||||
|
||||
// Verify channel still exists by trying to rejoin
|
||||
client.Write("JOIN #test");
|
||||
ircd.ReadUntil("JOIN #test");
|
||||
ircd.Write(":nick JOIN :#test");
|
||||
ircd.Write(":server 353 nick #test :nick");
|
||||
ircd.Write(":server 366 nick #test :End of /NAMES list");
|
||||
client.ReadUntil(":nick JOIN :#test");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace znc_inttest
|
||||
|
||||
Reference in New Issue
Block a user