1 /***************************************************************************
2 * Copyright (C) 2005-2020 by the Quassel Project *
3 * devel@quassel-irc.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #include "ircchannel.h"
22
23 #include <QDebug>
24 #include <QHashIterator>
25 #include <QMapIterator>
26 #include <QTextCodec>
27
28 #include "ircuser.h"
29 #include "network.h"
30 #include "util.h"
31
IrcChannel(const QString & channelname,Network * network)32 IrcChannel::IrcChannel(const QString& channelname, Network* network)
33 : SyncableObject(network)
34 , _initialized(false)
35 , _name(channelname)
36 , _topic(QString())
37 , _encrypted(false)
38 , _network(network)
39 , _codecForEncoding(nullptr)
40 , _codecForDecoding(nullptr)
41 {
42 setObjectName(QString::number(network->networkId().toInt()) + "/" + channelname);
43 }
44
45 // ====================
46 // PUBLIC:
47 // ====================
isKnownUser(IrcUser * ircuser) const48 bool IrcChannel::isKnownUser(IrcUser* ircuser) const
49 {
50 if (ircuser == nullptr) {
51 qWarning() << "Channel" << name() << "received IrcUser Nullpointer!";
52 return false;
53 }
54
55 if (!_userModes.contains(ircuser)) {
56 // This can happen e.g. when disconnecting from a network, so don't log a warning
57 return false;
58 }
59
60 return true;
61 }
62
isValidChannelUserMode(const QString & mode) const63 bool IrcChannel::isValidChannelUserMode(const QString& mode) const
64 {
65 bool isvalid = true;
66 if (mode.size() > 1) {
67 qWarning() << "Channel" << name() << "received Channel User Mode which is longer than 1 Char:" << mode;
68 isvalid = false;
69 }
70 return isvalid;
71 }
72
userModes(IrcUser * ircuser) const73 QString IrcChannel::userModes(IrcUser* ircuser) const
74 {
75 if (_userModes.contains(ircuser))
76 return _userModes[ircuser];
77 else
78 return QString();
79 }
80
userModes(const QString & nick) const81 QString IrcChannel::userModes(const QString& nick) const
82 {
83 return userModes(network()->ircUser(nick));
84 }
85
setCodecForEncoding(const QString & name)86 void IrcChannel::setCodecForEncoding(const QString& name)
87 {
88 setCodecForEncoding(QTextCodec::codecForName(name.toLatin1()));
89 }
90
setCodecForEncoding(QTextCodec * codec)91 void IrcChannel::setCodecForEncoding(QTextCodec* codec)
92 {
93 _codecForEncoding = codec;
94 }
95
setCodecForDecoding(const QString & name)96 void IrcChannel::setCodecForDecoding(const QString& name)
97 {
98 setCodecForDecoding(QTextCodec::codecForName(name.toLatin1()));
99 }
100
setCodecForDecoding(QTextCodec * codec)101 void IrcChannel::setCodecForDecoding(QTextCodec* codec)
102 {
103 _codecForDecoding = codec;
104 }
105
decodeString(const QByteArray & text) const106 QString IrcChannel::decodeString(const QByteArray& text) const
107 {
108 if (!codecForDecoding())
109 return network()->decodeString(text);
110 return ::decodeString(text, _codecForDecoding);
111 }
112
encodeString(const QString & string) const113 QByteArray IrcChannel::encodeString(const QString& string) const
114 {
115 if (codecForEncoding()) {
116 return _codecForEncoding->fromUnicode(string);
117 }
118 return network()->encodeString(string);
119 }
120
121 // ====================
122 // PUBLIC SLOTS:
123 // ====================
setTopic(const QString & topic)124 void IrcChannel::setTopic(const QString& topic)
125 {
126 _topic = topic;
127 SYNC(ARG(topic))
128 emit topicSet(topic);
129 }
130
setPassword(const QString & password)131 void IrcChannel::setPassword(const QString& password)
132 {
133 _password = password;
134 SYNC(ARG(password))
135 }
136
setEncrypted(bool encrypted)137 void IrcChannel::setEncrypted(bool encrypted)
138 {
139 _encrypted = encrypted;
140 SYNC(ARG(encrypted))
141 emit encryptedSet(encrypted);
142 }
143
joinIrcUsers(const QList<IrcUser * > & users,const QStringList & modes)144 void IrcChannel::joinIrcUsers(const QList<IrcUser*>& users, const QStringList& modes)
145 {
146 if (users.isEmpty())
147 return;
148
149 if (users.count() != modes.count()) {
150 qWarning() << "IrcChannel::addUsers(): number of nicks does not match number of modes!";
151 return;
152 }
153
154 // Sort user modes first
155 const QStringList sortedModes = network()->sortPrefixModes(modes);
156
157 QStringList newNicks;
158 QStringList newModes;
159 QList<IrcUser*> newUsers;
160
161 IrcUser* ircuser;
162 for (int i = 0; i < users.count(); i++) {
163 ircuser = users[i];
164 if (!ircuser)
165 continue;
166 if (_userModes.contains(ircuser)) {
167 if (sortedModes[i].count() > 1) {
168 // Multiple modes received, do it one at a time
169 // TODO Better way of syncing this without breaking protocol?
170 for (int i_m = 0; i_m < sortedModes[i].count(); ++i_m) {
171 addUserMode(ircuser, sortedModes[i][i_m]);
172 }
173 }
174 else {
175 addUserMode(ircuser, sortedModes[i]);
176 }
177 continue;
178 }
179
180 _userModes[ircuser] = sortedModes[i];
181 ircuser->joinChannel(this, true);
182 connect(ircuser, &IrcUser::nickSet, this, selectOverload<QString>(&IrcChannel::ircUserNickSet));
183
184 // connect(ircuser, SIGNAL(destroyed()), this, SLOT(ircUserDestroyed()));
185 // If you wonder why there is no counterpart to ircUserJoined:
186 // the joins are propagated by the ircuser. The signal ircUserJoined is only for convenience
187
188 newNicks << ircuser->nick();
189 newModes << sortedModes[i];
190 newUsers << ircuser;
191 }
192
193 if (newNicks.isEmpty())
194 return;
195
196 SYNC_OTHER(joinIrcUsers, ARG(newNicks), ARG(newModes));
197 emit ircUsersJoined(newUsers);
198 }
199
joinIrcUsers(const QStringList & nicks,const QStringList & modes)200 void IrcChannel::joinIrcUsers(const QStringList& nicks, const QStringList& modes)
201 {
202 QList<IrcUser*> users;
203 foreach (QString nick, nicks)
204 users << network()->newIrcUser(nick);
205 joinIrcUsers(users, modes);
206 }
207
joinIrcUser(IrcUser * ircuser)208 void IrcChannel::joinIrcUser(IrcUser* ircuser)
209 {
210 QList<IrcUser*> users;
211 users << ircuser;
212 QStringList modes;
213 modes << QString();
214 joinIrcUsers(users, modes);
215 }
216
part(IrcUser * ircuser)217 void IrcChannel::part(IrcUser* ircuser)
218 {
219 if (isKnownUser(ircuser)) {
220 _userModes.remove(ircuser);
221 ircuser->partChannel(this);
222 // If you wonder why there is no counterpart to ircUserParted:
223 // the joins are propagted by the ircuser. The signal ircUserParted is only for convenience
224 disconnect(ircuser, nullptr, this, nullptr);
225 emit ircUserParted(ircuser);
226
227 if (network()->isMe(ircuser) || _userModes.isEmpty()) {
228 // in either case we're no longer in the channel
229 // -> clean up the channel and destroy it
230 QList<IrcUser*> users = _userModes.keys();
231 _userModes.clear();
232 foreach (IrcUser* user, users) {
233 disconnect(user, nullptr, this, nullptr);
234 user->partChannelInternal(this, true);
235 }
236 emit parted();
237 network()->removeIrcChannel(this);
238 }
239 }
240 }
241
part(const QString & nick)242 void IrcChannel::part(const QString& nick)
243 {
244 part(network()->ircUser(nick));
245 }
246
247 // SET USER MODE
setUserModes(IrcUser * ircuser,const QString & modes)248 void IrcChannel::setUserModes(IrcUser* ircuser, const QString& modes)
249 {
250 if (isKnownUser(ircuser)) {
251 // Keep user modes sorted
252 _userModes[ircuser] = network()->sortPrefixModes(modes);
253 QString nick = ircuser->nick();
254 SYNC_OTHER(setUserModes, ARG(nick), ARG(modes))
255 emit ircUserModesSet(ircuser, modes);
256 }
257 }
258
setUserModes(const QString & nick,const QString & modes)259 void IrcChannel::setUserModes(const QString& nick, const QString& modes)
260 {
261 setUserModes(network()->ircUser(nick), modes);
262 }
263
264 // ADD USER MODE
addUserMode(IrcUser * ircuser,const QString & mode)265 void IrcChannel::addUserMode(IrcUser* ircuser, const QString& mode)
266 {
267 if (!isKnownUser(ircuser) || !isValidChannelUserMode(mode))
268 return;
269
270 if (!_userModes[ircuser].contains(mode)) {
271 // Keep user modes sorted
272 _userModes[ircuser] = network()->sortPrefixModes(_userModes[ircuser] + mode);
273 QString nick = ircuser->nick();
274 SYNC_OTHER(addUserMode, ARG(nick), ARG(mode))
275 emit ircUserModeAdded(ircuser, mode);
276 }
277 }
278
addUserMode(const QString & nick,const QString & mode)279 void IrcChannel::addUserMode(const QString& nick, const QString& mode)
280 {
281 addUserMode(network()->ircUser(nick), mode);
282 }
283
284 // REMOVE USER MODE
removeUserMode(IrcUser * ircuser,const QString & mode)285 void IrcChannel::removeUserMode(IrcUser* ircuser, const QString& mode)
286 {
287 if (!isKnownUser(ircuser) || !isValidChannelUserMode(mode))
288 return;
289
290 if (_userModes[ircuser].contains(mode)) {
291 // Removing modes shouldn't mess up ordering
292 _userModes[ircuser].remove(mode);
293 QString nick = ircuser->nick();
294 SYNC_OTHER(removeUserMode, ARG(nick), ARG(mode));
295 emit ircUserModeRemoved(ircuser, mode);
296 }
297 }
298
removeUserMode(const QString & nick,const QString & mode)299 void IrcChannel::removeUserMode(const QString& nick, const QString& mode)
300 {
301 removeUserMode(network()->ircUser(nick), mode);
302 }
303
304 // INIT SET USER MODES
initUserModes() const305 QVariantMap IrcChannel::initUserModes() const
306 {
307 QVariantMap usermodes;
308 QHash<IrcUser*, QString>::const_iterator iter = _userModes.constBegin();
309 while (iter != _userModes.constEnd()) {
310 usermodes[iter.key()->nick()] = iter.value();
311 ++iter;
312 }
313 return usermodes;
314 }
315
initSetUserModes(const QVariantMap & usermodes)316 void IrcChannel::initSetUserModes(const QVariantMap& usermodes)
317 {
318 QList<IrcUser*> users;
319 QStringList modes;
320 QVariantMap::const_iterator iter = usermodes.constBegin();
321 while (iter != usermodes.constEnd()) {
322 users << network()->newIrcUser(iter.key());
323 modes << iter.value().toString();
324 ++iter;
325 }
326 // joinIrcUsers handles sorting modes
327 joinIrcUsers(users, modes);
328 }
329
initChanModes() const330 QVariantMap IrcChannel::initChanModes() const
331 {
332 QVariantMap channelModes;
333
334 QVariantMap A_modes;
335 QHash<QChar, QStringList>::const_iterator A_iter = _A_channelModes.constBegin();
336 while (A_iter != _A_channelModes.constEnd()) {
337 A_modes[A_iter.key()] = A_iter.value();
338 ++A_iter;
339 }
340 channelModes["A"] = A_modes;
341
342 QVariantMap B_modes;
343 QHash<QChar, QString>::const_iterator B_iter = _B_channelModes.constBegin();
344 while (B_iter != _B_channelModes.constEnd()) {
345 B_modes[B_iter.key()] = B_iter.value();
346 ++B_iter;
347 }
348 channelModes["B"] = B_modes;
349
350 QVariantMap C_modes;
351 QHash<QChar, QString>::const_iterator C_iter = _C_channelModes.constBegin();
352 while (C_iter != _C_channelModes.constEnd()) {
353 C_modes[C_iter.key()] = C_iter.value();
354 ++C_iter;
355 }
356 channelModes["C"] = C_modes;
357
358 QString D_modes;
359 QSet<QChar>::const_iterator D_iter = _D_channelModes.constBegin();
360 while (D_iter != _D_channelModes.constEnd()) {
361 D_modes += *D_iter;
362 ++D_iter;
363 }
364 channelModes["D"] = D_modes;
365
366 return channelModes;
367 }
368
initSetChanModes(const QVariantMap & channelModes)369 void IrcChannel::initSetChanModes(const QVariantMap& channelModes)
370 {
371 QVariantMap::const_iterator iter = channelModes["A"].toMap().constBegin();
372 QVariantMap::const_iterator iterEnd = channelModes["A"].toMap().constEnd();
373 while (iter != iterEnd) {
374 _A_channelModes[iter.key()[0]] = iter.value().toStringList();
375 ++iter;
376 }
377
378 iter = channelModes["B"].toMap().constBegin();
379 iterEnd = channelModes["B"].toMap().constEnd();
380 while (iter != iterEnd) {
381 _B_channelModes[iter.key()[0]] = iter.value().toString();
382 ++iter;
383 }
384
385 iter = channelModes["C"].toMap().constBegin();
386 iterEnd = channelModes["C"].toMap().constEnd();
387 while (iter != iterEnd) {
388 _C_channelModes[iter.key()[0]] = iter.value().toString();
389 ++iter;
390 }
391
392 QString D_modes = channelModes["D"].toString();
393 for (int i = 0; i < D_modes.count(); i++) {
394 _D_channelModes << D_modes[i];
395 }
396 }
397
ircUserDestroyed()398 void IrcChannel::ircUserDestroyed()
399 {
400 auto* ircUser = static_cast<IrcUser*>(sender());
401 Q_ASSERT(ircUser);
402 _userModes.remove(ircUser);
403 // no further propagation.
404 // this leads only to fuck ups.
405 }
406
ircUserNickSet(QString nick)407 void IrcChannel::ircUserNickSet(QString nick)
408 {
409 auto* ircUser = qobject_cast<IrcUser*>(sender());
410 Q_ASSERT(ircUser);
411 emit ircUserNickSet(ircUser, nick);
412 }
413
414 /*******************************************************************************
415 *
416 * 3.3 CHANMODES
417 *
418 * o CHANMODES=A,B,C,D
419 *
420 * The CHANMODES token specifies the modes that may be set on a channel.
421 * These modes are split into four categories, as follows:
422 *
423 * o Type A: Modes that add or remove an address to or from a list.
424 * These modes always take a parameter when sent by the server to a
425 * client; when sent by a client, they may be specified without a
426 * parameter, which requests the server to display the current
427 * contents of the corresponding list on the channel to the client.
428 * o Type B: Modes that change a setting on the channel. These modes
429 * always take a parameter.
430 * o Type C: Modes that change a setting on the channel. These modes
431 * take a parameter only when set; the parameter is absent when the
432 * mode is removed both in the client's and server's MODE command.
433 * o Type D: Modes that change a setting on the channel. These modes
434 * never take a parameter.
435 *
436 * If the server sends any additional types after these 4, the client
437 * MUST ignore them; this is intended to allow future extension of this
438 * token.
439 *
440 * The IRC server MUST NOT list modes in CHANMODES which are also
441 * present in the PREFIX parameter; however, for completeness, modes
442 * described in PREFIX may be treated as type B modes.
443 *
444 ******************************************************************************/
445
446 /*******************************************************************************
447 * Short Version:
448 * A --> add/remove from List
449 * B --> set value or remove
450 * C --> set value or remove
451 * D --> on/off
452 *
453 * B and C behave very similar... we store the data in different datastructures
454 * for future compatibility
455 ******************************************************************************/
456
457 // NOTE: the behavior of addChannelMode and removeChannelMode depends on the type of mode
458 // see list above for chanmode types
addChannelMode(const QChar & mode,const QString & value)459 void IrcChannel::addChannelMode(const QChar& mode, const QString& value)
460 {
461 Network::ChannelModeType modeType = network()->channelModeType(mode);
462
463 switch (modeType) {
464 case Network::NOT_A_CHANMODE:
465 return;
466 case Network::A_CHANMODE:
467 if (!_A_channelModes.contains(mode))
468 _A_channelModes[mode] = QStringList(value);
469 else if (!_A_channelModes[mode].contains(value))
470 _A_channelModes[mode] << value;
471 break;
472
473 case Network::B_CHANMODE:
474 _B_channelModes[mode] = value;
475 break;
476
477 case Network::C_CHANMODE:
478 _C_channelModes[mode] = value;
479 break;
480
481 case Network::D_CHANMODE:
482 _D_channelModes << mode;
483 break;
484 }
485 SYNC(ARG(mode), ARG(value))
486 }
487
removeChannelMode(const QChar & mode,const QString & value)488 void IrcChannel::removeChannelMode(const QChar& mode, const QString& value)
489 {
490 Network::ChannelModeType modeType = network()->channelModeType(mode);
491
492 switch (modeType) {
493 case Network::NOT_A_CHANMODE:
494 return;
495 case Network::A_CHANMODE:
496 if (_A_channelModes.contains(mode))
497 _A_channelModes[mode].removeAll(value);
498 break;
499
500 case Network::B_CHANMODE:
501 _B_channelModes.remove(mode);
502 break;
503
504 case Network::C_CHANMODE:
505 _C_channelModes.remove(mode);
506 break;
507
508 case Network::D_CHANMODE:
509 _D_channelModes.remove(mode);
510 break;
511 }
512 SYNC(ARG(mode), ARG(value))
513 }
514
hasMode(const QChar & mode) const515 bool IrcChannel::hasMode(const QChar& mode) const
516 {
517 Network::ChannelModeType modeType = network()->channelModeType(mode);
518
519 switch (modeType) {
520 case Network::NOT_A_CHANMODE:
521 return false;
522 case Network::A_CHANMODE:
523 return _A_channelModes.contains(mode);
524 case Network::B_CHANMODE:
525 return _B_channelModes.contains(mode);
526 case Network::C_CHANMODE:
527 return _C_channelModes.contains(mode);
528 case Network::D_CHANMODE:
529 return _D_channelModes.contains(mode);
530 }
531 return false;
532 }
533
modeValue(const QChar & mode) const534 QString IrcChannel::modeValue(const QChar& mode) const
535 {
536 Network::ChannelModeType modeType = network()->channelModeType(mode);
537
538 switch (modeType) {
539 case Network::B_CHANMODE:
540 if (_B_channelModes.contains(mode))
541 return _B_channelModes[mode];
542 else
543 return QString();
544 case Network::C_CHANMODE:
545 if (_C_channelModes.contains(mode))
546 return _C_channelModes[mode];
547 else
548 return QString();
549 default:
550 return QString();
551 }
552 }
553
modeValueList(const QChar & mode) const554 QStringList IrcChannel::modeValueList(const QChar& mode) const
555 {
556 Network::ChannelModeType modeType = network()->channelModeType(mode);
557
558 switch (modeType) {
559 case Network::A_CHANMODE:
560 if (_A_channelModes.contains(mode))
561 return _A_channelModes[mode];
562 break;
563 default:
564 ;
565 }
566 return {};
567 }
568
channelModeString() const569 QString IrcChannel::channelModeString() const
570 {
571 QStringList params;
572 QString modeString;
573
574 QSet<QChar>::const_iterator D_iter = _D_channelModes.constBegin();
575 while (D_iter != _D_channelModes.constEnd()) {
576 modeString += *D_iter;
577 ++D_iter;
578 }
579
580 QHash<QChar, QString>::const_iterator BC_iter = _C_channelModes.constBegin();
581 while (BC_iter != _C_channelModes.constEnd()) {
582 modeString += BC_iter.key();
583 params << BC_iter.value();
584 ++BC_iter;
585 }
586
587 BC_iter = _B_channelModes.constBegin();
588 while (BC_iter != _B_channelModes.constEnd()) {
589 modeString += BC_iter.key();
590 params << BC_iter.value();
591 ++BC_iter;
592 }
593 if (modeString.isEmpty())
594 return modeString;
595 else
596 return QString("+%1 %2").arg(modeString).arg(params.join(" "));
597 }
598