diff --git a/src/common.cpp b/src/common.cpp index 61882594..6baf0d15 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -1,346 +1,346 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2004 Peter Simonsson Copyright (C) 2006-2008 Eike Hein Copyright (C) 2006 Michael Kreitzer */ #include "common.h" #include "application.h" #include "config/preferences.h" #include #include #include #include #include #include #include "guess_ja.cpp" #include "unicode.cpp" namespace Konversation { void initChanModesHash() { QHash myHash; myHash.insert(QLatin1Char('t'), i18n("topic protection")); myHash.insert(QLatin1Char('n'), i18n("no messages from outside")); myHash.insert(QLatin1Char('s'), i18n("secret")); myHash.insert(QLatin1Char('i'), i18n("invite only")); myHash.insert(QLatin1Char('p'), i18n("private")); myHash.insert(QLatin1Char('m'), i18n("moderated")); myHash.insert(QLatin1Char('k'), i18n("password protected")); myHash.insert(QLatin1Char('a'), i18n("anonymous")); myHash.insert(QLatin1Char('c'), i18n("no colors allowed")); myHash.insert(QLatin1Char('l'), i18n("user limit")); m_modesHash = myHash; } QHash getChannelModesHash() { if(m_modesHash.isEmpty()) initChanModesHash(); return m_modesHash; } QString removeIrcMarkup(const QString& text) { QString escaped(text); // Escape text decoration escaped.remove(colorRegExp); return escaped; } QString doVarExpansion(const QString& text) { if (!Preferences::self()->disableExpansion()) { QList > urlRanges = getUrlRanges(text); if (urlRanges.isEmpty()) return replaceFormattingCodes(text); else { QString line; QPair pair; int startPos = 0; int length = 0; QListIterator > i(urlRanges); while (i.hasNext()) { pair = i.next(); length = pair.first - startPos; line += replaceFormattingCodes(text.mid(startPos, length)); startPos = pair.first + pair.second; - line += text.mid(pair.first, pair.second); + line += text.midRef(pair.first, pair.second); } if (startPos <= text.length() - 1) line += replaceFormattingCodes(text.mid(startPos)); return line; } } return text; } QString replaceFormattingCodes(const QString& text) { QString line = text; // Replace placeholders. line.replace(QStringLiteral("%%"),QStringLiteral("%\x01")); // make sure to protect double %% line.replace(QStringLiteral("%B"),QStringLiteral("\x02")); // replace %B with bold char line.replace(QStringLiteral("%C"),QStringLiteral("\x03")); // replace %C with color char line.replace(QStringLiteral("%G"),QStringLiteral("\x07")); // replace %G with ASCII BEL 0x07 line.replace(QStringLiteral("%I"),QStringLiteral("\x1d")); // replace %I with italics char line.replace(QStringLiteral("%O"),QStringLiteral("\x0f")); // replace %O with reset to default char line.replace(QStringLiteral("%S"),QStringLiteral("\x13")); // replace %S with strikethru char // line.replace(QRegExp("%?"),"\x15"); line.replace(QStringLiteral("%R"),QStringLiteral("\x16")); // replace %R with reverse char line.replace(QStringLiteral("%U"),QStringLiteral("\x1f")); // replace %U with underline char line.replace(QStringLiteral("%\x01"),QStringLiteral("%")); // restore double %% as single % return line; } QString replaceIRCMarkups(const QString& text) { QString line(text); line.replace(QLatin1Char('\x02'), QStringLiteral("%B")); // replace bold char with %B line.replace(QLatin1Char('\x03'), QStringLiteral("%C")); // replace color char with %C line.replace(QLatin1Char('\x07'), QStringLiteral("%G")); // replace ASCII BEL 0x07 with %G line.replace(QLatin1Char('\x1d'), QStringLiteral("%I")); // replace italics char with %I line.replace(QLatin1Char('\x0f'), QStringLiteral("%O")); // replace reset to default char with %O line.replace(QLatin1Char('\x13'), QStringLiteral("%S")); // replace strikethru char with %S line.replace(QLatin1Char('\x16'), QStringLiteral("%R")); // replace reverse char with %R // underline char send by kvirc line.replace(QLatin1Char('\x1f'), QStringLiteral("%U")); // replace underline char with %U // underline char send by mirc line.replace(QLatin1Char('\x15'), QStringLiteral("%U")); // replace underline char with %U return line; } QList > getUrlRanges(const QString& text) { TextUrlData data = extractUrlData(text, false); return data.urlRanges; } QList< QPair< int, int > > getChannelRanges(const QString& text) { TextChannelData data = extractChannelData(text, false); return data.channelRanges; } TextUrlData extractUrlData(const QString& text, bool doUrlFixup) { TextUrlData data; QString htmlText(text); urlPattern.setCaseSensitivity(Qt::CaseInsensitive); int pos = 0; int urlLen = 0; QString protocol; QString href; while ((pos = urlPattern.indexIn(htmlText, pos)) >= 0) { urlLen = urlPattern.matchedLength(); href = htmlText.mid(pos, urlLen); data.urlRanges << QPair(pos, href.length()); pos += href.length(); if (doUrlFixup) { protocol.clear(); if (urlPattern.cap(2).isEmpty()) { QString urlPatternCap1(urlPattern.cap(1)); if (urlPatternCap1.contains(QLatin1Char('@'))) protocol = QStringLiteral("mailto:"); else if (urlPatternCap1.startsWith(QLatin1String("ftp."), Qt::CaseInsensitive)) protocol = QStringLiteral("ftp://"); else protocol = QStringLiteral("http://"); } href = protocol + removeIrcMarkup(href); data.fixedUrls.append(href); } } return data; } TextChannelData extractChannelData(const QString& text, bool doChannelFixup) { TextChannelData data; QString ircText(text); int pos = 0; int chanLen = 0; QString channel; while ((pos = chanExp.indexIn(ircText, pos)) >= 0) { channel = chanExp.cap(2); chanLen = channel.length(); // we want the pos where #channel starts // indexIn gives us the first match and the first match may be // "#test", " #test" or " \"test", so the first Index is off by some chars pos = chanExp.pos(2); data.channelRanges << QPair(pos, chanLen); pos += chanLen; if (doChannelFixup) { channel = removeIrcMarkup(channel); data.fixedChannels.append(channel); } } return data; } bool isUrl(const QString& text) { return urlPattern.exactMatch(text); } QString extractColorCodes(const QString& _text) { QString text(_text); int pos = 0; QString ret; QString match; while ((pos = colorRegExp.indexIn(text, pos)) >= 0) { match = colorRegExp.cap(0); ret += match; text.remove(pos, match.length()); } return ret; } QPixmap overlayPixmaps( const QPixmap &under, const QPixmap &over ) { if (over.isNull() && under.isNull()) return QPixmap(); else if (under.isNull()) return QPixmap(over); else if (over.isNull()) return QPixmap(under); QPixmap result(under); QPainter painter(&result); painter.drawPixmap(QPoint(0,0), over); return result; } uint colorForNick(const QString& nickname) { int nickvalue = 0; for (int index = 0; index < nickname.length(); index++) { nickvalue += nickname[index].unicode(); } return (nickvalue % 8); } /// Replace invalid codepoints so the string can be converted to Utf8. /// @param s a const reference to the QString to copy and change /// @retval s new QString QString sterilizeUnicode(const QString& s) { QString copy(s); sterilizeUnicode(copy); return copy; } /// Replace invalid codepoints so the string can be converted to Utf8. /// @param s a reference to the QString to change, a reference so it works with m_inputbuffer.back() in server.cpp /// @retval s reference to the argument QString& sterilizeUnicode(QString& s) { // HACK work around undocumented requirement to vet Unicode text sent over DBUS. // strips noncharacters, the private use characters are presumably safe for (int i = 0; i < s.length(); ++i) { QChar c(s.at(i)); if (c.category() == QChar::Other_Surrogate) { if (!c.isHighSurrogate() || (!(i+1 < s.length()) && !s.at(i+1).isLowSurrogate())) { //stomp on this bad char now, next trip through the loop (if there is one) eats the other s.replace(i, 1, QChar(0xFFFD)); continue; } QChar next = s.at(i+1); if ((next.unicode()&0x3FE) == 0x3FE && (c.unicode()&0x3F) == 0x3F) { s.replace(i, 1, QChar(0xFFFD)); //its one of the last two of the plane, replace them s.replace(i+1, 1, QChar(0xFFFD)); } ++i; // skip the high surrogate now, the loop takes care of the low } else if ((c.category() == QChar::Other_NotAssigned) //perhaps Qt will use QChar::Other_NotAssigned some day || (c.unicode() >= 0xFDD0 && c.unicode() <= 0xFDEF) //Unicode class Cn on BMP only || (c.unicode() == 0xFFFE || (c.unicode() == 0xFFFF)) //Unicode class Cn on all planes ) { s.replace(i, 1, QChar(0xFFFD)); } } return s; } /// Run a QStringList through sterilizeUnicode /// @param list a reference to the list /// @retval list QStringList& sterilizeUnicode(QStringList& list) { for (int i = 0; i < list.count(); ++i) sterilizeUnicode(list[i]); return list; } /// Copy the list argument and return it, filtered QStringList sterilizeUnicode(const QStringList& list) { QStringList out(list); return sterilizeUnicode(out); } } diff --git a/src/irc/server.cpp b/src/irc/server.cpp index 87ec200e..185e027a 100644 --- a/src/irc/server.cpp +++ b/src/irc/server.cpp @@ -1,4408 +1,4408 @@ // -*- mode: c++; c-file-style: "bsd"; c-basic-offset: 4; tabs-width: 4; indent-tabs-mode: nil -*- /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005 Ismail Donmez Copyright (C) 2005-2016 Peter Simonsson Copyright (C) 2006-2008 Eli J. MacKenzie Copyright (C) 2005-2008 Eike Hein */ #include "server.h" #include "ircqueue.h" #include "query.h" #include "channel.h" #include "application.h" #include "connectionmanager.h" #include "dcccommon.h" #include "dccfiledialog.h" #include "transfermanager.h" #include "transfersend.h" #include "transferrecv.h" #include "chat.h" #include "recipientdialog.h" #include "nick.h" #include "irccharsets.h" #include "viewcontainer.h" #include "rawlog.h" #include "channellistpanel.h" #include "scriptlauncher.h" #include "serverison.h" #include "notificationhandler.h" #include "awaymanager.h" #include "ircinput.h" #include #include #include #include #include #include #include #include #include #include using namespace Konversation; int Server::m_availableConnectionId = 0; Server::Server(QObject* parent, ConnectionSettings& settings) : QObject(parent) { m_connectionId = m_availableConnectionId; m_availableConnectionId++; setConnectionSettings(settings); m_connectionState = Konversation::SSNeverConnected; m_recreationScheduled = false; m_delayedConnectTimer = new QTimer(this); m_delayedConnectTimer->setSingleShot(true); connect(m_delayedConnectTimer, SIGNAL(timeout()), this, SLOT(connectToIRCServer())); m_reconnectImmediately = false; for (int i=0; i <= Application::instance()->countOfQueues(); i++) { //QList r=Preferences::queueRate(i); IRCQueue *q=new IRCQueue(this, Application::instance()->staticrates[i]); //FIXME these are supposed to be in the rc m_queues.append(q); } m_processingIncoming = false; m_identifyMsg = false; m_capRequested = 0; m_capAnswered = 0; m_capEndDelayed = false; m_autoJoin = false; m_capabilities = NoCapabilies; initCapablityNames(); m_nickIndices.clear(); m_nickIndices.append(0); m_nickListModel = new QStringListModel(this); m_currentLag = -1; m_rawLog = nullptr; m_channelListPanel = nullptr; m_serverISON = nullptr; m_away = false; m_socket = nullptr; m_prevISONList = QStringList(); m_bytesReceived = 0; m_encodedBytesSent=0; m_bytesSent=0; m_linesSent=0; // TODO fold these into a QMAP, and these need to be reset to RFC values if this server object is reused. m_serverNickPrefixModes = QStringLiteral("ovh"); m_serverNickPrefixes = QStringLiteral("@+%"); m_banAddressListModes = QLatin1Char('b'); // {RFC-1459, draft-brocklesby-irc-isupport} -> pick one m_channelPrefixes = QStringLiteral("#&"); m_modesCount = 3; m_sslErrorLock = false; m_topicLength = -1; setObjectName(QString::fromLatin1("server_") + m_connectionSettings.name()); setNickname(m_connectionSettings.initialNick()); obtainNickInfo(getNickname()); m_statusView = getViewContainer()->addStatusView(this); if (Preferences::self()->rawLog()) addRawLog(false); m_inputFilter.setServer(this); m_outputFilter = new Konversation::OutputFilter(this); // For /msg query completion m_completeQueryPosition = 0; updateAutoJoin(m_connectionSettings.oneShotChannelList()); if (!getIdentity()->getShellCommand().isEmpty()) QTimer::singleShot(0, this, SLOT(doPreShellCommand())); else QTimer::singleShot(0, this, SLOT(connectToIRCServer())); initTimers(); if (getIdentity()->getShellCommand().isEmpty()) connectSignals(); // TODO FIXME this disappeared in a merge, ensure it should have updateConnectionState(Konversation::SSNeverConnected); m_nickInfoChangedTimer = new QTimer(this); m_nickInfoChangedTimer->setSingleShot(true); m_nickInfoChangedTimer->setInterval(3000); connect(m_nickInfoChangedTimer, SIGNAL(timeout()), this, SLOT(sendNickInfoChangedSignals())); m_channelNickChangedTimer = new QTimer(this); m_channelNickChangedTimer->setSingleShot(true); m_channelNickChangedTimer->setInterval(1000); connect(m_channelNickChangedTimer, SIGNAL(timeout()), this, SLOT(sendChannelNickChangedSignals())); } Server::~Server() { //send queued messages qDebug() << "Server::~Server(" << getServerName() << ")"; // Delete helper object. delete m_serverISON; m_serverISON = nullptr; // clear nicks online emit nicksNowOnline(this,QStringList(),true); // Make sure no signals get sent to a soon to be dying Server Window if (m_socket) { m_socket->blockSignals(true); m_socket->deleteLater(); } delete m_statusView; closeRawLog(); closeChannelListPanel(); if (m_recreationScheduled) { Konversation::ChannelList channelList; foreach (Channel* channel, m_channelList) { channelList << channel->channelSettings(); } m_connectionSettings.setOneShotChannelList(channelList); } qDeleteAll(m_channelList); m_channelList.clear(); m_loweredChannelNameHash.clear(); qDeleteAll(m_queryList); m_queryList.clear(); purgeData(); //Delete the queues qDeleteAll(m_queues); emit destroyed(m_connectionId); if (m_recreationScheduled) { qRegisterMetaType("ConnectionSettings"); qRegisterMetaType("Konversation::ConnectionFlag"); Application* konvApp = Application::instance(); QMetaObject::invokeMethod(konvApp->getConnectionManager(), "connectTo", Qt::QueuedConnection, Q_ARG(Konversation::ConnectionFlag, Konversation::CreateNewConnection), Q_ARG(ConnectionSettings, m_connectionSettings)); } qDebug() << "~Server done"; } void Server::purgeData() { // Delete all the NickInfos and ChannelNick structures. m_allNicks.clear(); ChannelMembershipMap::ConstIterator it; for ( it = m_joinedChannels.constBegin(); it != m_joinedChannels.constEnd(); ++it ) delete it.value(); m_joinedChannels.clear(); for ( it = m_unjoinedChannels.constBegin(); it != m_unjoinedChannels.constEnd(); ++it ) delete it.value(); m_unjoinedChannels.clear(); m_queryNicks.clear(); delete m_serverISON; m_serverISON = nullptr; } //... so called to match the ChatWindow derivatives. bool Server::closeYourself(bool askForConfirmation) { m_statusView->closeYourself(askForConfirmation); return true; } void Server::cycle() { m_recreationScheduled = true; m_statusView->closeYourself(); } void Server::doPreShellCommand() { KShell::Errors e; QStringList command = KShell::splitArgs(getIdentity()->getShellCommand(), KShell::TildeExpand, &e); if (e != KShell::NoError) { //FIXME The flow needs to be refactored, add a finally-like method that does the ready-to-connect stuff // "The pre-connect shell command could not be understood!"); preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus()); } else { // FIXME add i18n, and in preShellCommandExited and preShellCommandError getStatusView()->appendServerMessage(i18n("Info"), i18nc("The command mentioned is executed in a shell prior to connecting.", "Running pre-connect shell command...")); connect(&m_preShellCommand, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(preShellCommandExited(int,QProcess::ExitStatus))); connect(&m_preShellCommand, SIGNAL(error(QProcess::ProcessError)), SLOT(preShellCommandError(QProcess::ProcessError))); m_preShellCommand.setProgram(command); m_preShellCommand.start(); // NOTE: isConnecting is tested in connectToIRCServer so there's no guard here if (m_preShellCommand.state() == QProcess::NotRunning) preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus()); } } void Server::initTimers() { m_notifyTimer.setObjectName(QStringLiteral("notify_timer")); m_notifyTimer.setSingleShot(true); m_incomingTimer.setObjectName(QStringLiteral("incoming_timer")); m_pingSendTimer.setSingleShot(true); } void Server::connectSignals() { // Timers connect(&m_incomingTimer, SIGNAL(timeout()), this, SLOT(processIncomingData())); connect(&m_notifyTimer, SIGNAL(timeout()), this, SLOT(notifyTimeout())); connect(&m_pingResponseTimer, SIGNAL(timeout()), this, SLOT(updateLongPongLag())); connect(&m_pingSendTimer, SIGNAL(timeout()), this, SLOT(sendPing())); // OutputFilter connect(getOutputFilter(), SIGNAL(requestDccSend()), this,SLOT(requestDccSend()), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(requestDccSend(QString)), this, SLOT(requestDccSend(QString)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(multiServerCommand(QString,QString)), this, SLOT(sendMultiServerCommand(QString,QString))); connect(getOutputFilter(), SIGNAL(reconnectServer(QString)), this, SLOT(reconnectServer(QString))); connect(getOutputFilter(), SIGNAL(disconnectServer(QString)), this, SLOT(disconnectServer(QString))); connect(getOutputFilter(), SIGNAL(quitServer(QString)), this, SLOT(quitServer(QString))); connect(getOutputFilter(), SIGNAL(openDccSend(QString,QUrl)), this, SLOT(addDccSend(QString,QUrl)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(openDccChat(QString)), this, SLOT(openDccChat(QString)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(openDccWBoard(QString)), this, SLOT(openDccWBoard(QString)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(acceptDccGet(QString,QString)), this, SLOT(acceptDccGet(QString,QString))); connect(getOutputFilter(), SIGNAL(sendToAllChannels(QString)), this, SLOT(sendToAllChannels(QString))); connect(getOutputFilter(), SIGNAL(banUsers(QStringList,QString,QString)), this, SLOT(requestBan(QStringList,QString,QString))); connect(getOutputFilter(), SIGNAL(unbanUsers(QString,QString)), this, SLOT(requestUnban(QString,QString))); connect(getOutputFilter(), SIGNAL(openRawLog(bool)), this, SLOT(addRawLog(bool))); connect(getOutputFilter(), SIGNAL(closeRawLog()), this, SLOT(closeRawLog())); connect(getOutputFilter(), SIGNAL(encodingChanged()), this, SLOT(updateEncoding())); Application* konvApp = Application::instance(); connect(getOutputFilter(), SIGNAL(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool)), konvApp->getConnectionManager(), SLOT(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool))); connect(konvApp->getDccTransferManager(), SIGNAL(newDccTransferQueued(Konversation::DCC::Transfer*)), this, SLOT(slotNewDccTransferItemQueued(Konversation::DCC::Transfer*))); // ViewContainer connect(this, SIGNAL(showView(ChatWindow*)), getViewContainer(), SLOT(showView(ChatWindow*))); connect(this, SIGNAL(addDccPanel()), getViewContainer(), SLOT(addDccPanel())); connect(this, SIGNAL(addDccChat(Konversation::DCC::Chat*)), getViewContainer(), SLOT(addDccChat(Konversation::DCC::Chat*)), Qt::QueuedConnection); connect(this, SIGNAL(serverLag(Server*,int)), getViewContainer(), SIGNAL(updateStatusBarLagLabel(Server*,int))); connect(this, SIGNAL(tooLongLag(Server*,int)), getViewContainer(), SIGNAL(setStatusBarLagLabelTooLongLag(Server*,int))); connect(this, SIGNAL(resetLag(Server*)), getViewContainer(), SIGNAL(resetStatusBarLagLabel(Server*))); connect(getOutputFilter(), SIGNAL(showView(ChatWindow*)), getViewContainer(), SLOT(showView(ChatWindow*))); connect(getOutputFilter(), SIGNAL(openKonsolePanel()), getViewContainer(), SLOT(addKonsolePanel())); connect(getOutputFilter(), SIGNAL(openChannelList(QString)), this, SLOT(requestOpenChannelListPanel(QString))); connect(getOutputFilter(), SIGNAL(closeDccPanel()), getViewContainer(), SLOT(closeDccPanel())); connect(getOutputFilter(), SIGNAL(addDccPanel()), getViewContainer(), SLOT(addDccPanel())); // Inputfilter - queued connections should be used for slots that have blocking UI connect(&m_inputFilter, SIGNAL(addDccChat(QString,QStringList)), this, SLOT(addDccChat(QString,QStringList)), Qt::QueuedConnection); connect(&m_inputFilter, SIGNAL(rejectDccChat(QString)), this, SLOT(rejectDccChat(QString))); connect(&m_inputFilter, SIGNAL(startReverseDccChat(QString,QStringList)), this, SLOT(startReverseDccChat(QString,QStringList))); connect(&m_inputFilter, SIGNAL(welcome(QString)), this, SLOT(capCheckIgnored())); connect(&m_inputFilter, SIGNAL(welcome(QString)), this, SLOT(connectionEstablished(QString))); connect(&m_inputFilter, SIGNAL(notifyResponse(QString)), this, SLOT(notifyResponse(QString))); connect(&m_inputFilter, SIGNAL(startReverseDccSendTransfer(QString,QStringList)), this, SLOT(startReverseDccSendTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(addDccGet(QString,QStringList)), this, SLOT(addDccGet(QString,QStringList)), Qt::QueuedConnection); connect(&m_inputFilter, SIGNAL(resumeDccGetTransfer(QString,QStringList)), this, SLOT(resumeDccGetTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(resumeDccSendTransfer(QString,QStringList)), this, SLOT(resumeDccSendTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(rejectDccSendTransfer(QString,QStringList)), this, SLOT(rejectDccSendTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(userhost(QString,QString,bool,bool)), this, SLOT(userhost(QString,QString,bool,bool)) ); connect(&m_inputFilter, SIGNAL(topicAuthor(QString,QString,QDateTime)), this, SLOT(setTopicAuthor(QString,QString,QDateTime)) ); connect(&m_inputFilter, SIGNAL(endOfWho(QString)), this, SLOT(endOfWho(QString)) ); connect(&m_inputFilter, SIGNAL(endOfNames(QString)), this, SLOT(endOfNames(QString)) ); connect(&m_inputFilter, SIGNAL(invitation(QString,QString)), this,SLOT(invitation(QString,QString)) ); connect(&m_inputFilter, SIGNAL(addToChannelList(QString,int,QString)), this, SLOT(addToChannelList(QString,int,QString))); // Status View connect(this, SIGNAL(serverOnline(bool)), getStatusView(), SLOT(serverOnline(bool))); // Scripts connect(getOutputFilter(), SIGNAL(launchScript(int,QString,QString)), konvApp->getScriptLauncher(), SLOT(launchScript(int,QString,QString))); connect(konvApp->getScriptLauncher(), SIGNAL(scriptNotFound(QString)), this, SLOT(scriptNotFound(QString))); connect(konvApp->getScriptLauncher(), SIGNAL(scriptExecutionError(QString)), this, SLOT(scriptExecutionError(QString))); connect(Preferences::self(), SIGNAL(notifyListStarted(int)), this, SLOT(notifyListStarted(int)), Qt::QueuedConnection); } int Server::getPort() { return getConnectionSettings().server().port(); } int Server::getLag() const { return m_currentLag; } bool Server::getAutoJoin() const { return m_autoJoin; } void Server::setAutoJoin(bool on) { m_autoJoin = on; } void Server::preShellCommandExited(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); if (exitStatus == QProcess::NormalExit) getStatusView()->appendServerMessage(i18n("Info"), i18n("Pre-shell command executed successfully!")); else { QString errorText = i18nc("An error message from KDE or Qt is appended.", "There was a problem while executing the command: ") % m_preShellCommand.errorString(); getStatusView()->appendServerMessage(i18n("Warning"), errorText); } connectToIRCServer(); connectSignals(); } void Server::preShellCommandError(QProcess::ProcessError error) { Q_UNUSED(error); QString errorText = i18nc("An error message from KDE or Qt is appended.", "There was a problem while executing the command: ") % m_preShellCommand.errorString(); getStatusView()->appendServerMessage(i18n("Warning"), errorText); connectToIRCServer(); connectSignals(); } void Server::connectToIRCServer() { if (!isConnected() && !isConnecting()) { if (m_sslErrorLock) { qDebug() << "Refusing to connect while SSL lock from previous connection attempt is being held."; return; } // Reenable check when it works reliably for all backends // if(Solid::Networking::status() != Solid::Networking::Connected) // { // updateConnectionState(Konversation::SSInvoluntarilyDisconnected); // return; // } updateConnectionState(Konversation::SSConnecting); m_ownIpByUserhost.clear(); resetQueues(); // This is needed to support server groups with mixed SSL and nonSSL servers delete m_socket; m_socket = nullptr; if (m_referenceNicklist != getIdentity()->getNicknameList()) m_nickListModel->setStringList(getIdentity()->getNicknameList()); resetNickSelection(); m_socket = new KTcpSocket(); m_socket->setObjectName(QStringLiteral("serverSocket")); connect(m_socket, SIGNAL(error(KTcpSocket::Error)), SLOT(broken(KTcpSocket::Error)) ); connect(m_socket, SIGNAL(readyRead()), SLOT(incoming())); connect(m_socket, SIGNAL(disconnected()), SLOT(closed())); connect(m_socket, SIGNAL(hostFound()), SLOT (hostFound())); getStatusView()->appendServerMessage(i18n("Info"),i18n("Looking for server %1 (port %2)...", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); KTcpSocket::ProxyPolicy proxyPolicy = KTcpSocket::AutoProxy; if(getConnectionSettings().server().bypassProxy()) { proxyPolicy = KTcpSocket::ManualProxy; m_socket->setProxy(QNetworkProxy::NoProxy); } // connect() will do a async lookup too if (getConnectionSettings().server().SSLEnabled() || getIdentity()->getAuthType() == QStringLiteral("saslexternal") || getIdentity()->getAuthType() == QStringLiteral("pemclientcert")) { connect(m_socket, SIGNAL(encrypted()), SLOT (socketConnected())); connect(m_socket, SIGNAL(sslErrors(QList)), SLOT(sslError(QList))); if (getIdentity()->getAuthType() == QStringLiteral("saslexternal") || getIdentity()->getAuthType() == QStringLiteral("pemclientcert")) { m_socket->setLocalCertificate(getIdentity()->getPemClientCertFile().toLocalFile()); m_socket->setPrivateKey(getIdentity()->getPemClientCertFile().toLocalFile()); } m_socket->setAdvertisedSslVersion(KTcpSocket::SecureProtocols); m_socket->connectToHostEncrypted(getConnectionSettings().server().host(), getConnectionSettings().server().port()); } else { connect(m_socket, SIGNAL(connected()), SLOT (socketConnected())); m_socket->connectToHost(getConnectionSettings().server().host(), getConnectionSettings().server().port(), proxyPolicy); } // set up the connection details setPrefixes(m_serverNickPrefixModes, m_serverNickPrefixes); // reset InputFilter (auto request info, /WHO request info) m_inputFilter.reset(); } else qDebug() << "connectToIRCServer() called while already connected: This should never happen. (" << (isConnecting() << 1) + isConnected() << ')'; } void Server::connectToIRCServerIn(uint delay) { m_delayedConnectTimer->setInterval(delay * 1000); m_delayedConnectTimer->start(); updateConnectionState(Konversation::SSScheduledToConnect); } void Server::showSSLDialog() { //TODO /* SSLSocket* sslsocket = dynamic_cast(m_socket); if (sslsocket) sslsocket->showInfoDialog(); */ } void Server::rebuildTargetPrefixMatcher() { m_targetMatcher.setPattern(QLatin1String("^([") + getServerNickPrefixes() + QLatin1String("]*)([") + getChannelTypes() + QLatin1String("])(.+)")); } // set available channel types according to 005 RPL_ISUPPORT void Server::setChannelTypes(const QString &pre) { m_channelPrefixes = pre; rebuildTargetPrefixMatcher(); if (getConnectionSettings().reconnectCount() == 0) { updateAutoJoin(m_connectionSettings.oneShotChannelList()); } else { updateAutoJoin(); } } QString Server::getChannelTypes() const { return m_channelPrefixes; } // set max number of channel modes with parameter according to 005 RPL_ISUPPORT void Server::setModesCount(int count) { m_modesCount = count; } int Server::getModesCount() { return m_modesCount; } // set user mode prefixes according to non-standard 005-Reply (see inputfilter.cpp) void Server::setPrefixes(const QString &modes, const QString& prefixes) { // NOTE: serverModes is QString::null, if server did not supply the // modes which relates to the network's nick-prefixes m_serverNickPrefixModes = modes; m_serverNickPrefixes = prefixes; rebuildTargetPrefixMatcher(); } QString Server::getServerNickPrefixes() const { return m_serverNickPrefixes; } void Server::setChanModes(const QString& modes) { QStringList abcd = modes.split(QLatin1Char(',')); m_banAddressListModes = abcd.value(0); } // return a nickname without possible mode character at the beginning void Server::mangleNicknameWithModes(QString& nickname,bool& isAdmin,bool& isOwner, bool& isOp,bool& isHalfop,bool& hasVoice) { isAdmin = false; isOwner = false; isOp = false; isHalfop = false; hasVoice = false; int modeIndex; if (nickname.isEmpty()) return; while ((modeIndex = m_serverNickPrefixes.indexOf(nickname[0])) != -1) { if(nickname.isEmpty()) return; nickname = nickname.mid(1); // cut off the prefix bool recognisedMode = false; // determine, whether status is like op or like voice while(modeIndex < m_serverNickPrefixes.length() && !recognisedMode) { switch(m_serverNickPrefixes[modeIndex].toLatin1()) { case '*': // admin (EUIRC) { isAdmin = true; recognisedMode = true; break; } case '&': // admin (unrealircd) { isAdmin = true; recognisedMode = true; break; } case '!': // channel owner (RFC2811) { isOwner = true; recognisedMode = true; break; } case '~': // channel owner (unrealircd) { isOwner = true; recognisedMode = true; break; } case '@': // channel operator (RFC1459) { isOp = true; recognisedMode = true; break; } case '%': // halfop { isHalfop = true; recognisedMode = true; break; } case '+': // voiced (RFC1459) { hasVoice = true; recognisedMode = true; break; } default: { ++modeIndex; break; } } //switch to recognise the mode. } // loop through the modes to find one recognised } // loop through the name } void Server::hostFound() { getStatusView()->appendServerMessage(i18n("Info"),i18n("Server found, connecting...")); } void Server::socketConnected() { emit sslConnected(this); getConnectionSettings().setReconnectCount(0); requestAvailableCapabilies(); QStringList ql; if (getIdentity() && getIdentity()->getAuthType() == QStringLiteral("serverpw") && !getIdentity()->getAuthPassword().isEmpty()) { ql << QStringLiteral("PASS ") + getIdentity()->getAuthPassword(); } else if (!getConnectionSettings().server().password().isEmpty()) ql << QStringLiteral("PASS ") + getConnectionSettings().server().password(); ql << QStringLiteral("NICK ") + getNickname(); ql << QStringLiteral("USER ") + getIdentity()->getIdent() + QStringLiteral(" 8 * :") /* 8 = +i; 4 = +w */ + getIdentity()->getRealName(); queueList(ql, HighPriority); connect(this, SIGNAL(nicknameChanged(QString)), getStatusView(), SLOT(setNickname(QString))); setNickname(getNickname()); } void Server::requestAvailableCapabilies () { m_capRequested = 0; m_capAnswered = 0; m_capEndDelayed = false; m_capabilities = NoCapabilies; getStatusView()->appendServerMessage(i18n("Info"),i18n("Negotiating capabilities with server...")); m_inputFilter.setAutomaticRequest(QStringLiteral("CAP LS"), QString(), true); queue(QStringLiteral("CAP LS 302"), HighPriority); } void Server::capInitiateNegotiation(const QString &availableCaps) { QStringList requestCaps; QStringList capsList = availableCaps.split (QChar(' '), QString::SkipEmptyParts); QStringList nameValue; foreach(const QString &cap, capsList) { nameValue = cap.split(QChar('=')); if(nameValue[0] == QStringLiteral("sasl")) { QString authCommand; if (getIdentity()) { // A username is optional SASL EXTERNAL and a client cert substitutes // for the password. if (getIdentity()->getAuthType() == QStringLiteral("saslexternal")) { authCommand = QStringLiteral("EXTERNAL"); // PLAIN on the other hand requires both. } else if (getIdentity()->getAuthType() == QStringLiteral("saslplain") && !getIdentity()->getSaslAccount().isEmpty() && !getIdentity()->getAuthPassword().isEmpty()) { authCommand = QStringLiteral("PLAIN"); } } if(!authCommand.isEmpty()) { QSet supportedSaslTypes; if(nameValue.count() > 1) supportedSaslTypes = QSet::fromList(nameValue[1].split(QChar(','))); if(!supportedSaslTypes.isEmpty() && !supportedSaslTypes.contains(authCommand)) getStatusView()->appendServerMessage(i18n("Error"), i18n("Server does not support %1 as SASL authentication mechanism, skipping SASL authentication.", authCommand)); else requestCaps.append ("sasl"); } } else if(m_capabilityNames.contains(nameValue[0])) { requestCaps.append (nameValue[0]); } } QString capsString = requestCaps.join (QStringLiteral(" ")); getStatusView()->appendServerMessage(i18n("Info"),i18n("Requesting capabilities: %1", capsString)); queue(QStringLiteral("CAP REQ :") + capsString, HighPriority); m_capRequested++; } void Server::capReply() { m_capAnswered++; } void Server::capEndNegotiation() { if(m_capRequested == m_capAnswered) { getStatusView()->appendServerMessage(i18n("Info"),i18n("Closing capabilities negotiation.")); queue(QStringLiteral("CAP END"), HighPriority); } } void Server::capCheckIgnored() { if (m_capRequested && !m_capAnswered) getStatusView()->appendServerMessage(i18n("Error"), i18n("Capabilities negotiation failed: Appears not supported by server.")); } void Server::capAcknowledged(const QString& name, Server::CapModifiers modifiers) { if (name == QStringLiteral("sasl") && modifiers == Server::NoModifiers) { const QString &authCommand = (getIdentity()->getAuthType() == QStringLiteral("saslexternal")) ? QStringLiteral("EXTERNAL") : QStringLiteral("PLAIN"); getStatusView()->appendServerMessage(i18n("Info"), i18n("SASL capability acknowledged by server, attempting SASL %1 authentication...", authCommand)); sendAuthenticate(authCommand); m_capEndDelayed = true; } m_capabilities |= m_capabilityNames.value(name); } void Server::capDenied(const QString& name) { if (name == QStringLiteral("sasl")) getStatusView()->appendServerMessage(i18n("Error"), i18n("SASL capability denied or not supported by server.")); } void Server::registerWithServices() { if (!getIdentity()) return; NickInfoPtr nickInfo = getNickInfo(getNickname()); if (nickInfo && nickInfo->isIdentified()) return; if (getIdentity()->getAuthType() == QStringLiteral("nickserv")) { if (!getIdentity()->getNickservNickname().isEmpty() && !getIdentity()->getNickservCommand().isEmpty() && !getIdentity()->getAuthPassword().isEmpty()) { queue(QStringLiteral("PRIVMSG ")+getIdentity()->getNickservNickname()+QStringLiteral(" :")+getIdentity()->getNickservCommand()+QLatin1Char(' ')+getIdentity()->getAuthPassword(), HighPriority); } } else if (getIdentity()->getAuthType() == QStringLiteral("saslplain")) { QString authString = getIdentity()->getSaslAccount(); authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getSaslAccount()); authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getAuthPassword()); sendAuthenticate(QLatin1String(authString.toLatin1().toBase64())); } else if (getIdentity()->getAuthType() == QStringLiteral("saslexternal")) { QString authString = getIdentity()->getSaslAccount(); // An account name is optional with SASL EXTERNAL. if (!authString.isEmpty()) { authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getSaslAccount()); } sendAuthenticate(QLatin1String(authString.toLatin1().toBase64())); } } void Server::sendAuthenticate(const QString& message) { m_lastAuthenticateCommand = message; if (message.isEmpty()) { queue(QStringLiteral("AUTHENTICATE +"), HighPriority); } else { queue(QStringLiteral("AUTHENTICATE ") + message, HighPriority); } } void Server::broken(KTcpSocket::Error error) { Q_UNUSED(error); qDebug() << "Connection broken with state" << m_connectionState << "and error:" << m_socket->errorString(); m_socket->blockSignals(true); resetQueues(); m_notifyTimer.stop(); m_pingSendTimer.stop(); m_pingResponseTimer.stop(); m_inputFilter.setLagMeasuring(false); m_currentLag = -1; purgeData(); emit resetLag(this); emit nicksNowOnline(this, QStringList(), true); m_prevISONList.clear(); updateAutoJoin(); if (m_sslErrorLock) { // We got disconnected while dealing with an SSL error, e.g. due to the // user taking their time on dealing with the error dialog. Since auto- // reconnecting makes no sense in this situation, let's pass it off as a // deliberate disconnect. sslError() will either kick off a reconnect, or // reaffirm this. getStatusView()->appendServerMessage(i18n("SSL Connection Error"), i18n("Connection to server %1 (port %2) lost while waiting for user response to an SSL error. " "Will automatically reconnect if error is ignored.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); updateConnectionState(SSDeliberatelyDisconnected); } else if (getConnectionState() == Konversation::SSDeliberatelyDisconnected) { if (m_reconnectImmediately) { m_reconnectImmediately = false; QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } } else { Application::instance()->notificationHandler()->connectionFailure(getStatusView(), getServerName()); QString error = i18n("Connection to server %1 (port %2) lost: %3.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()), m_socket->errorString()); getStatusView()->appendServerMessage(i18n("Error"), error); updateConnectionState(Konversation::SSInvoluntarilyDisconnected); } // HACK Only show one nick change dialog at connection time. // This hack is a bit nasty as it assumes that the only QInputDialog // child of the statusview will be the nick change dialog. if (getStatusView()) { QInputDialog* nickChangeDialog = getStatusView()->findChild(); if (nickChangeDialog) nickChangeDialog->reject(); } } void Server::sslError( const QList& errors ) { // We have to explicitly grab the socket we got the error from, // lest we might end up calling ignoreSslErrors() on a different // socket later if m_socket has started pointing at something // else. QPointer socket = qobject_cast(QObject::sender()); m_sslErrorLock = true; bool ignoreSslErrors = KIO::SslUi::askIgnoreSslErrors(socket, KIO::SslUi::RecallAndStoreRules); m_sslErrorLock = false; // The dialog-based user interaction above may take an undefined amount // of time, and anything could happen to the socket in that span of time. // If it was destroyed, let's not do anything and bail out. if (!socket) { qDebug() << "Socket was destroyed while waiting for user interaction."; return; } // Ask the user if he wants to ignore the errors. if (ignoreSslErrors) { // Show a warning in the chat window that the SSL certificate failed the authenticity check. QString error = i18n("The SSL certificate for the server %1 (port %2) failed the authenticity check.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port())); getStatusView()->appendServerMessage(i18n("SSL Connection Warning"), error); // We may have gotten disconnected while waiting for user response and have // to reconnect. if (isConnecting()) { // The user has chosen to ignore SSL errors. socket->ignoreSslErrors(); } else { // QueuedConnection is vital here, otherwise we're deleting the socket // in a slot connected to one of its signals (connectToIRCServer deletes // any old socket) and crash. QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } } else { // Don't auto-reconnect if the user chose to ignore the SSL errors -- // treat it as a deliberate disconnect. updateConnectionState(Konversation::SSDeliberatelyDisconnected); QString errorReason; for (int i = 0; i < errors.size(); ++i) { errorReason += errors.at(i).errorString() + QLatin1Char(' '); } QString error = i18n("Could not connect to %1 (port %2) using SSL encryption. Either the server does not support SSL (did you use the correct port?) or you rejected the certificate. %3", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()), errorReason); getStatusView()->appendServerMessage(i18n("SSL Connection Error"), error); emit sslInitFailure(); } } // Will be called from InputFilter as soon as the Welcome message was received void Server::connectionEstablished(const QString& ownHost) { // Some servers don't include the userhost in RPL_WELCOME, so we // need to use RPL_USERHOST to get ahold of our IP later on if (!ownHost.isEmpty()) QHostInfo::lookupHost(ownHost, this, SLOT(gotOwnResolvedHostByWelcome(QHostInfo))); updateConnectionState(Konversation::SSConnected); // Make a helper object to build ISON (notify) list. // TODO: Give the object a kick to get it started? delete m_serverISON; m_serverISON = new ServerISON(this); // get first notify very early startNotifyTimer(1000); // Register with services if (getIdentity() && getIdentity()->getAuthType() == QStringLiteral("nickserv")) registerWithServices(); // get own ip by userhost requestUserhost(getNickname()); // Start the PINGPONG match m_pingSendTimer.start(1000 /*1 sec*/); // Recreate away state if we were set away prior to a reconnect. if (m_away) { // Correct server's beliefs about its away state. m_away = false; requestAway(m_awayReason); } } //FIXME operator[] inserts an empty T& so each destination might just as well have its own key storage QByteArray Server::getKeyForRecipient(const QString& recipient) const { return m_keyHash[recipient.toLower()]; } void Server::setKeyForRecipient(const QString& recipient, const QByteArray& key) { m_keyHash[recipient.toLower()] = key; } void Server::gotOwnResolvedHostByWelcome(const QHostInfo& res) { if (res.error() == QHostInfo::NoError && !res.addresses().isEmpty()) m_ownIpByWelcome = res.addresses().first().toString(); else qDebug() << "Got error: " << res.errorString(); } bool Server::isSocketConnected() const { if (!m_socket) return false; return (m_socket->state() == KTcpSocket::ConnectedState); } void Server::updateConnectionState(Konversation::ConnectionState state) { if (state != m_connectionState) { m_connectionState = state; if (m_connectionState == Konversation::SSConnected) emit serverOnline(true); else if (m_connectionState != Konversation::SSConnecting) emit serverOnline(false); emit connectionStateChanged(this, state); } } void Server::reconnectServer(const QString& quitMessage) { if (isConnecting() || isSocketConnected()) { m_reconnectImmediately = true; quitServer(quitMessage); } else QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } void Server::disconnectServer(const QString& quitMessage) { getConnectionSettings().setReconnectCount(0); if (isScheduledToConnect()) { m_delayedConnectTimer->stop(); getStatusView()->appendServerMessage(i18n("Info"), i18n("Delayed connect aborted.")); } if (isSocketConnected()) quitServer(quitMessage); } void Server::quitServer(const QString& quitMessage) { // Make clear this is deliberate even if the QUIT never actually goes through the queue // (i.e. this is not redundant with _send_internal()'s updateConnectionState() call for // a QUIT). updateConnectionState(Konversation::SSDeliberatelyDisconnected); if (!m_socket) return; QString toServer = QStringLiteral("QUIT :"); if (quitMessage.isEmpty()) toServer += getIdentity()->getQuitReason(); else toServer += quitMessage; queue(toServer, HighPriority); flushQueues(); m_socket->flush(); // Close the socket to allow a dead connection to be reconnected before the socket timeout. m_socket->close(); getStatusView()->appendServerMessage(i18n("Info"), i18n("Disconnected from %1 (port %2).", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); } void Server::notifyAction(const QString& nick) { QString out(Preferences::self()->notifyDoubleClickAction()); OutputFilter::replaceAliases(out); // parse wildcards (toParse,nickname,channelName,nickList,parameter) out = parseWildcards(out, getNickname(), QString(), QString(), nick, QString()); // Send all strings, one after another QStringList outList = out.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (int index=0; indexparse(getNickname(),outList[index],QString()); queue(result.toServer); } // endfor } void Server::notifyResponse(const QString& nicksOnline) { bool nicksOnlineChanged = false; QStringList actualList = nicksOnline.split(QLatin1Char(' '), QString::SkipEmptyParts); QString lcActual = QLatin1Char(' ') + nicksOnline + QLatin1Char(' '); QString lcPrevISON = QLatin1Char(' ') + (m_prevISONList.join(QStringLiteral(" "))) + QLatin1Char(' '); QStringList::iterator it; //Are any nicks gone offline for (it = m_prevISONList.begin(); it != m_prevISONList.end(); ++it) { if (!lcActual.contains(QLatin1Char(' ') + (*it) + QLatin1Char(' '), Qt::CaseInsensitive)) { setNickOffline(*it); nicksOnlineChanged = true; } } //Are any nicks gone online for (it = actualList.begin(); it != actualList.end(); ++it) { if (!lcPrevISON.contains(QLatin1Char(' ') + (*it) + QLatin1Char(' '), Qt::CaseInsensitive)) { setWatchedNickOnline(*it); nicksOnlineChanged = true; } } // Note: The list emitted in this signal *does* include nicks in joined channels. emit nicksNowOnline(this, actualList, nicksOnlineChanged); m_prevISONList = actualList; // Next round startNotifyTimer(); } void Server::notifyListStarted(int serverGroupId) { if (getServerGroup()) if (getServerGroup()->id() == serverGroupId) startNotifyTimer(1000); } void Server::startNotifyTimer(int msec) { // make sure the timer gets started properly in case we have reconnected m_notifyTimer.stop(); if (Preferences::self()->useNotify()) { if (msec == 0) msec = Preferences::self()->notifyDelay() * 1000; m_notifyTimer.start(msec); } } void Server::notifyTimeout() { // Notify delay time is over, send ISON request if desired if (Preferences::self()->useNotify()) { // But only if there actually are nicks in the notify list QString list = getISONListString(); if (!list.isEmpty()) queue(QStringLiteral("ISON ") + list, LowPriority); } } void Server::autoCommandsAndChannels() { if (getServerGroup() && !getServerGroup()->connectCommands().isEmpty()) { QString connectCommands = getServerGroup()->connectCommands(); if (!getNickname().isEmpty()) connectCommands.replace(QStringLiteral("%nick"), getNickname()); QStringList connectCommandsList = connectCommands.split(QLatin1Char(';'), QString::SkipEmptyParts); QStringList::iterator iter; for (iter = connectCommandsList.begin(); iter != connectCommandsList.end(); ++iter) { QString output(*iter); output = output.simplified(); OutputFilter::replaceAliases(output); Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),output,QString()); queue(result.toServer); } } if (getAutoJoin()) { for ( QStringList::Iterator it = m_autoJoinCommands.begin(); it != m_autoJoinCommands.end(); ++it ) queue((*it)); } if (!m_connectionSettings.oneShotChannelList().isEmpty()) { QStringList oneShotJoin = generateJoinCommand(m_connectionSettings.oneShotChannelList()); for ( QStringList::Iterator it = oneShotJoin.begin(); it != oneShotJoin.end(); ++it ) { queue((*it)); } m_connectionSettings.clearOneShotChannelList(); } } /** Create a set of indices into the nickname list of the current identity based on the current nickname. * * The index list is only used if the current nickname is not available. If the nickname is in the identity, * we do not want to retry it. If the nickname is not in the identity, it is considered to be at position -1. */ void Server::resetNickSelection() { m_nickIndices.clear(); // For equivalence testing in case the identity gets changed underneath us. m_referenceNicklist = getIdentity()->getNicknameList(); for (int i = 0; i < m_referenceNicklist.length(); ++i) { // Pointless to include the nick we're already going to use. if (m_referenceNicklist.at(i) != getNickname()) { m_nickIndices.append(i); } } } QString Server::getNextNickname() { //if the identity changed underneath us (likely impossible), start over if (m_referenceNicklist != getIdentity()->getNicknameList()) resetNickSelection(); QString newNick; if (!m_nickIndices.isEmpty()) { newNick = getIdentity()->getNickname(m_nickIndices.takeFirst()); } else { QString inputText = i18n("No nicknames from the \"%1\" identity were accepted by the connection \"%2\".\nPlease enter a new one or press Cancel to disconnect:", getIdentity()->getName(), getDisplayName()); newNick = QInputDialog::getText(getStatusView(), i18n("Nickname error"), inputText); } return newNick; } void Server::processIncomingData() { m_incomingTimer.stop(); if (!m_inputBuffer.isEmpty() && !m_processingIncoming) { m_processingIncoming = true; QString front(m_inputBuffer.front()); m_inputBuffer.pop_front(); m_inputFilter.parseLine(front); m_processingIncoming = false; if (!m_inputBuffer.isEmpty()) m_incomingTimer.start(0); } } void Server::incoming() { //if (getConnectionSettings().server().SSLEnabled()) // emit sslConnected(this); //if (len <= 0 && getConnectionSettings().server().SSLEnabled()) // return; // split buffer to lines QList bufferLines; while (m_socket->canReadLine()) { QByteArray line(m_socket->readLine()); //remove \n blowfish doesn't like it int i = line.size()-1; while (i >= 0 && (line[i]=='\n' || line[i]=='\r')) // since euIRC gets away with sending just \r, bet someone sends \n\r? { i--; } line.truncate(i+1); if (line.size() > 0) bufferLines.append(line); } while(!bufferLines.isEmpty()) { // Pre parsing is needed in case encryption/decryption is needed // BEGIN set channel encoding if specified QString senderNick; bool isServerMessage = false; QString channelKey; QTextCodec* codec = getIdentity()->getCodec(); QByteArray first = bufferLines.first(); bufferLines.removeFirst(); QStringList lineSplit = codec->toUnicode(first).split(QLatin1Char(' '), QString::SkipEmptyParts); if (lineSplit.count() >= 1) { if (lineSplit[0][0] == QLatin1Char(':')) // does this message have a prefix? { if(!lineSplit[0].contains(QLatin1Char('!'))) // is this a server(global) message? isServerMessage = true; else senderNick = lineSplit[0].mid(1, lineSplit[0].indexOf(QLatin1Char('!'))-1); lineSplit.removeFirst(); // remove prefix } } if (lineSplit.isEmpty()) continue; // BEGIN pre-parse to know where the message belongs to QString command = lineSplit[0].toLower(); if( isServerMessage ) { if( lineSplit.count() >= 3 ) { if( command == QStringLiteral("332") ) // RPL_TOPIC channelKey = lineSplit[2]; if( command == QStringLiteral("372") ) // RPL_MOTD channelKey = QStringLiteral(":server"); } } else // NOT a global message { if( lineSplit.count() >= 2 ) { // query if( ( command == QStringLiteral("privmsg") || command == QStringLiteral("notice") ) && lineSplit[1] == getNickname() ) { channelKey = senderNick; } // channel message else if( command == QStringLiteral("privmsg") || command == QStringLiteral("notice") || command == QStringLiteral("join") || command == QStringLiteral("kick") || command == QStringLiteral("part") || command == QStringLiteral("topic") ) { channelKey = lineSplit[1]; } } } // END pre-parse to know where the message belongs to // Decrypt if necessary //send to raw log before decryption if (m_rawLog) m_rawLog->appendRaw(RawLog::Inbound, first); #ifdef HAVE_QCA2 QByteArray cKey = getKeyForRecipient(channelKey); if(!cKey.isEmpty()) { if(command == "privmsg") { //only send encrypted text to decrypter int index = first.indexOf(":",first.indexOf(":")+1); if(this->identifyMsgEnabled()) // Workaround braindead Freenode prefixing messages with + ++index; QByteArray backup = first.mid(0,index+1); if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey)) first = getChannelByName(channelKey)->getCipher()->decrypt(first.mid(index+1)); else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey)) first = getQueryByName(channelKey)->getCipher()->decrypt(first.mid(index+1)); first.prepend(backup); } else if(command == "332" || command == "topic") { //only send encrypted text to decrypter int index = first.indexOf(":",first.indexOf(":")+1); QByteArray backup = first.mid(0,index+1); if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey)) first = getChannelByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1)); else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey)) first = getQueryByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1)); first.prepend(backup); } } #endif bool isUtf8 = Konversation::isUtf8(first); QString encoded; if (isUtf8) encoded = QString::fromUtf8(first); else { // check setting QString channelEncoding; if( !channelKey.isEmpty() ) { if(getServerGroup()) channelEncoding = Preferences::channelEncoding(getServerGroup()->id(), channelKey); else channelEncoding = Preferences::channelEncoding(getDisplayName(), channelKey); } // END set channel encoding if specified if( !channelEncoding.isEmpty() ) codec = Konversation::IRCCharsets::self()->codecForName(channelEncoding); // if channel encoding is utf-8 and the string is definitely not utf-8 // then try latin-1 if (codec->mibEnum() == 106) codec = QTextCodec::codecForMib( 4 /* iso-8859-1 */ ); encoded = codec->toUnicode(first); } // Qt uses 0xFDD0 and 0xFDD1 to mark the beginning and end of text frames. Remove // these here to avoid fatal errors encountered in QText* and the event loop pro- // cessing. sterilizeUnicode(encoded); if (!encoded.isEmpty()) m_inputBuffer << encoded; //FIXME: This has nothing to do with bytes, and it's not raw received bytes either. Bogus number. //m_bytesReceived+=m_inputBuffer.back().length(); } if( !m_incomingTimer.isActive() && !m_processingIncoming ) m_incomingTimer.start(0); } /** Calculate how long this message premable will be. This is necessary because the irc server will clip messages so that the client receives a maximum of 512 bytes at once. */ int Server::getPreLength(const QString& command, const QString& dest) { NickInfoPtr info = getNickInfo(getNickname()); int hostMaskLength = 0; if(info) hostMaskLength = info->getHostmask().length(); //:Sho_!i=ehs1@konversation/developer/hein PRIVMSG #konversation :and then back to it //$nickname$hostmask$command$destination$message int x= 512 - 8 - (m_nickname.length() + hostMaskLength + command.length() + dest.length()); return x; } //Commands greater than 1 have localizeable text: 0 1 2 3 4 5 6 static QStringList outcmds = QString(QStringLiteral("WHO QUIT PRIVMSG NOTICE KICK PART TOPIC")).split(QChar(QLatin1Char(' '))); int Server::_send_internal(QString outputLine) { QStringList outputLineSplit = outputLine.split(QLatin1Char(' '), QString::SkipEmptyParts); int outboundCommand = -1; if (!outputLineSplit.isEmpty()) { //Lets cache the uppercase command so we don't miss or reiterate too much outboundCommand = outcmds.indexOf(outputLineSplit[0].toUpper()); } if (outputLine.at(outputLine.length()-1) == QLatin1Char('\n')) { qDebug() << "found \\n on " << outboundCommand; outputLine.resize(outputLine.length()-1); } // remember the first arg of /WHO to identify responses if (outboundCommand == 0 && outputLineSplit.count() >= 2) //"WHO" m_inputFilter.addWhoRequest(outputLineSplit[1]); else if (outboundCommand == 1) //"QUIT" updateConnectionState(Konversation::SSDeliberatelyDisconnected); // set channel encoding if specified QString channelCodecName; //[ PRIVMSG | NOTICE | KICK | PART | TOPIC ] target :message if (outputLineSplit.count() > 2 && outboundCommand > 1) { if(getServerGroup()) // if we're connecting via a servergroup channelCodecName=Preferences::channelEncoding(getServerGroup()->id(), outputLineSplit[1]); else //if we're connecting to a server manually channelCodecName=Preferences::channelEncoding(getDisplayName(), outputLineSplit[1]); } QTextCodec* codec = nullptr; if (channelCodecName.isEmpty()) codec = getIdentity()->getCodec(); else codec = Konversation::IRCCharsets::self()->codecForName(channelCodecName); // Some codecs don't work with a negative value. This is a bug in Qt 3. // ex.: JIS7, eucJP, SJIS //int outlen=-1; //leaving this done twice for now, I'm uncertain of the implications of not encoding other commands QByteArray encoded = outputLine.toUtf8(); if(codec) encoded = codec->fromUnicode(outputLine); #ifdef HAVE_QCA2 QString cipherKey; if (outputLineSplit.count() > 2 && outboundCommand > 1) cipherKey = getKeyForRecipient(outputLineSplit.at(1)); if (!cipherKey.isEmpty()) { int colon = outputLine.indexOf(':'); if (colon > -1) { colon++; QString pay(outputLine.mid(colon)); //only encode the actual user text, IRCD *should* desire only ASCII 31 < x < 127 for protocol elements QByteArray payload = pay.toUtf8(); QByteArray dest; if (codec) { payload=codec->fromUnicode(pay); //apparently channel name isn't a protocol element... dest = codec->fromUnicode(outputLineSplit.at(1)); } else { dest = outputLineSplit.at(1).toLatin1(); } if (outboundCommand == 2 || outboundCommand == 6) // outboundCommand == 3 { bool doit = true; if (outboundCommand == 2) { //if its a privmsg and a ctcp but not an action, don't encrypt //not interpreting `payload` in case encoding bollixed it if (outputLineSplit.at(2).startsWith(QLatin1String(":\x01")) && outputLineSplit.at(2) != ":\x01""ACTION") doit = false; } if (doit) { QString target = outputLineSplit.at(1); if(getChannelByName(target) && getChannelByName(target)->getCipher()->setKey(cipherKey.toLocal8Bit())) getChannelByName(target)->getCipher()->encrypt(payload); else if(getQueryByName(target) && getQueryByName(target)->getCipher()->setKey(cipherKey.toLocal8Bit())) getQueryByName(target)->getCipher()->encrypt(payload); encoded = outputLineSplit.at(0).toLatin1(); qDebug() << payload << "\n" << payload.data(); //two lines because the compiler insists on using the wrong operator+ encoded += ' ' + dest + " :" + payload; } } } } #endif if (m_rawLog) m_rawLog->appendRaw(RawLog::Outbound, encoded); encoded += '\n'; qint64 sout = m_socket->write(encoded); return sout; } void Server::toServer(QString&s, IRCQueue* q) { int sizesent = _send_internal(s); emit sentStat(s.length(), sizesent, q); //tell the queues what we sent collectStats(s.length(), sizesent); } void Server::collectStats(int bytes, int encodedBytes) { m_bytesSent += bytes; m_encodedBytesSent += encodedBytes; m_linesSent++; } bool Server::validQueue(QueuePriority priority) { if (priority >=0 && priority <= Application::instance()->countOfQueues()) return true; return false; } bool Server::queue(const QString& line, QueuePriority priority) { if (!line.isEmpty() && validQueue(priority)) { IRCQueue& out=*m_queues[priority]; out.enqueue(line); return true; } return false; } bool Server::queueList(const QStringList& buffer, QueuePriority priority) { if (buffer.isEmpty() || !validQueue(priority)) return false; IRCQueue& out=*(m_queues[priority]); for(int i=0;icountOfQueues(); i++) m_queues[i]->reset(); } //this could flood you off, but you're leaving anyway... void Server::flushQueues() { int cue; do { cue=-1; int wait=0; for (int i=1; i <= Application::instance()->countOfQueues(); i++) //slow queue can rot { IRCQueue *queue=m_queues[i]; //higher queue indices have higher priorty, higher queue priority wins tie if (!queue->isEmpty() && queue->currentWait()>=wait) { cue=i; wait=queue->currentWait(); } } if (cue>-1) m_queues[cue]->sendNow(); } while (cue>-1); } void Server::closed() { broken(m_socket->error()); } void Server::dbusRaw(const QString& command) { if(command.startsWith(Preferences::self()->commandChar())) { queue(command.section(Preferences::self()->commandChar(), 1)); } else queue(command); } void Server::dbusSay(const QString& target,const QString& command) { if(isAChannel(target)) { Channel* channel=getChannelByName(target); if(channel) channel->sendText(command); } else { Query* query = getQueryByName(target); if(query==nullptr) { NickInfoPtr nickinfo = obtainNickInfo(target); query=addQuery(nickinfo, true); } if(query) { if(!command.isEmpty()) query->sendText(command); else { query->adjustFocus(); getViewContainer()->getWindow()->show(); KWindowSystem::demandAttention(getViewContainer()->getWindow()->winId()); KWindowSystem::activateWindow(getViewContainer()->getWindow()->winId()); } } } } void Server::dbusInfo(const QString& string) { appendMessageToFrontmost(i18n("D-Bus"),string); } void Server::ctcpReply(const QString &receiver,const QString &text) { queue(QStringLiteral("NOTICE ")+receiver+QStringLiteral(" :")+QLatin1Char('\x01')+text+QLatin1Char('\x01')); } // Given a nickname, returns NickInfo object. 0 if not found. NickInfoPtr Server::getNickInfo(const QString& nickname) { QString lcNickname(nickname.toLower()); if (m_allNicks.contains(lcNickname)) { NickInfoPtr nickinfo = m_allNicks[lcNickname]; Q_ASSERT(nickinfo); return nickinfo; } else return NickInfoPtr(); //! TODO FIXME null null null } // Given a nickname, returns an existing NickInfo object, or creates a new NickInfo object. // Returns pointer to the found or created NickInfo object. NickInfoPtr Server::obtainNickInfo(const QString& nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(QString(nickname.toLower()), nickInfo); } return nickInfo; } const NickInfoMap* Server::getAllNicks() { return &m_allNicks; } // Returns the list of members for a channel in the joinedChannels list. // 0 if channel is not in the joinedChannels list. // Using code must not alter the list. const ChannelNickMap *Server::getJoinedChannelMembers(const QString& channelName) const { QString lcChannelName = channelName.toLower(); if (m_joinedChannels.contains(lcChannelName)) return m_joinedChannels[lcChannelName]; else return nullptr; } // Returns the list of members for a channel in the unjoinedChannels list. // 0 if channel is not in the unjoinedChannels list. // Using code must not alter the list. const ChannelNickMap *Server::getUnjoinedChannelMembers(const QString& channelName) const { QString lcChannelName = channelName.toLower(); if (m_unjoinedChannels.contains(lcChannelName)) return m_unjoinedChannels[lcChannelName]; else return nullptr; } // Searches the Joined and Unjoined lists for the given channel and returns the member list. // 0 if channel is not in either list. // Using code must not alter the list. const ChannelNickMap *Server::getChannelMembers(const QString& channelName) const { const ChannelNickMap *members = getJoinedChannelMembers(channelName); if (members) return members; else return getUnjoinedChannelMembers(channelName); } // Returns pointer to the ChannelNick (mode and pointer to NickInfo) for a given channel and nickname. // 0 if not found. ChannelNickPtr Server::getChannelNick(const QString& channelName, const QString& nickname) { QString lcNickname = nickname.toLower(); const ChannelNickMap *channelNickMap = getChannelMembers(channelName); if (channelNickMap) { if (channelNickMap->contains(lcNickname)) return (*channelNickMap)[lcNickname]; else return ChannelNickPtr(); //! TODO FIXME null null null } else { return ChannelNickPtr(); //! TODO FIXME null null null } } // Updates a nickname in a channel. If not on the joined or unjoined lists, and nick // is in the watch list, adds the channel and nick to the unjoinedChannels list. // If mode != 99, sets the mode for the nick in the channel. // Returns the NickInfo object if nick is on any lists, otherwise 0. ChannelNickPtr Server::setChannelNick(const QString& channelName, const QString& nickname, unsigned int mode) { QString lcNickname = nickname.toLower(); // If already on a list, update mode. ChannelNickPtr channelNick = getChannelNick(channelName, lcNickname); if (!channelNick) { // If the nick is on the watch list, add channel and nick to unjoinedChannels list. if (getWatchList().contains(lcNickname, Qt::CaseInsensitive)) { channelNick = addNickToUnjoinedChannelsList(channelName, nickname); channelNick->setMode(mode); } else return ChannelNickPtr(); //! TODO FIXME null null null } if (mode != 99) channelNick->setMode(mode); return channelNick; } // Returns a list of all the joined channels that a nick is in. QStringList Server::getNickJoinedChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } // Returns a list of all the channels (joined or unjoined) that a nick is in. QStringList Server::getNickChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } for( channel = m_unjoinedChannels.constBegin(); channel != m_unjoinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } QStringList Server::getSharedChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } bool Server::isNickOnline(const QString &nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); return (nickInfo != nullptr); } QString Server::getOwnIpByNetworkInterface() { return m_socket->localAddress().toString(); } QString Server::getOwnIpByServerMessage() { if(!m_ownIpByWelcome.isEmpty()) return m_ownIpByWelcome; else if(!m_ownIpByUserhost.isEmpty()) return m_ownIpByUserhost; else return QString(); } Query* Server::addQuery(const NickInfoPtr & nickInfo, bool weinitiated) { QString nickname = nickInfo->getNickname(); // Only create new query object if there isn't already one with the same name Query* query = getQueryByName(nickname); if (!query) { QString lcNickname = nickname.toLower(); query = getViewContainer()->addQuery(this, nickInfo, weinitiated); query->indicateAway(m_away); connect(query, SIGNAL(sendFile(QString)),this, SLOT(requestDccSend(QString))); connect(this, SIGNAL(serverOnline(bool)), query, SLOT(serverOnline(bool))); // Append query to internal list m_queryList.append(query); m_queryNicks.insert(lcNickname, nickInfo); if (!weinitiated) Application::instance()->notificationHandler()->query(query, nickname); } else if (weinitiated) { emit showView(query); } // try to get hostmask if there's none yet if (query->getNickInfo()->getHostmask().isEmpty()) requestUserhost(nickname); Q_ASSERT(query); return query; } void Server::closeQuery(const QString &name) { Query* query = getQueryByName(name); removeQuery(query); // Update NickInfo. If no longer on any lists, delete it altogether, but // only if not on the watch list. ISON replies will determine whether the NickInfo // is deleted altogether in that case. QString lcNickname = name.toLower(); m_queryNicks.remove(lcNickname); if (!isWatchedNick(name)) deleteNickIfUnlisted(name); } void Server::closeChannel(const QString& name) { qDebug() << "Server::closeChannel(" << name << ")"; Channel* channelToClose = getChannelByName(name); if (channelToClose && channelToClose->joined()) { Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), Preferences::self()->commandChar() + QStringLiteral("PART"), name); queue(result.toServer); } } void Server::requestChannelList() { m_inputFilter.setAutomaticRequest(QStringLiteral("LIST"), QString(), true); queue(QStringLiteral("LIST")); } void Server::requestWhois(const QString& nickname) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHOIS"), nickname, true); queue(QStringLiteral("WHOIS ")+nickname, LowPriority); } void Server::requestWho(const QString& channel) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHO"), channel, true); QString command(QStringLiteral("WHO ") + channel); if (capabilities() & WHOX && capabilities() & ExtendedJoin) { // Request the account as well as the usual info. // See http://faerion.sourceforge.net/doc/irc/whox.var // for more info. command += QStringLiteral(" nuhsa%cuhsnfdra"); } queue(command, LowPriority); } void Server::requestUserhost(const QString& nicks) { const QStringList nicksList = nicks.split(QLatin1Char(' '), QString::SkipEmptyParts); for(QStringList::ConstIterator it=nicksList.constBegin() ; it!=nicksList.constEnd() ; ++it) m_inputFilter.setAutomaticRequest(QStringLiteral("USERHOST"), *it, true); queue(QStringLiteral("USERHOST ")+nicks, LowPriority); } void Server::requestTopic(const QString& channel) { m_inputFilter.setAutomaticRequest(QStringLiteral("TOPIC"), channel, true); queue(QStringLiteral("TOPIC ")+channel, LowPriority); } void Server::resolveUserhost(const QString& nickname) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHOIS"), nickname, true); m_inputFilter.setAutomaticRequest(QStringLiteral("DNS"), nickname, true); queue(QStringLiteral("WHOIS ")+nickname, LowPriority); //FIXME when is this really used? } void Server::requestBan(const QStringList& users,const QString& channel,const QString& a_option) { QString option=a_option.toLower(); Channel* targetChannel=getChannelByName(channel); for(int index=0;indexgetNickByName(mask); // if we found the nick try to find their hostmask if(targetNick) { QString hostmask=targetNick->getChannelNick()->getHostmask(); // if we found the hostmask, add it to the ban mask if(!hostmask.isEmpty()) { mask=targetNick->getChannelNick()->getNickname()+QLatin1Char('!')+hostmask; // adapt ban mask to the option given if(option==QStringLiteral("host")) mask=QStringLiteral("*!*@*.")+hostmask.section(QLatin1Char('.'),1); else if(option==QStringLiteral("domain")) mask=QStringLiteral("*!*@")+hostmask.section(QLatin1Char('@'),1); else if(option==QStringLiteral("userhost")) mask=QStringLiteral("*!")+hostmask.section(QLatin1Char('@'),0,0)+QStringLiteral("@*.")+hostmask.section(QLatin1Char('.'),1); else if(option==QStringLiteral("userdomain")) mask=QStringLiteral("*!")+hostmask.section(QLatin1Char('@'),0,0)+QLatin1Char('@')+hostmask.section(QLatin1Char('@'),1); } } } Konversation::OutputFilterResult result = getOutputFilter()->execBan(mask,channel); queue(result.toServer); } } void Server::requestUnban(const QString& mask,const QString& channel) { Konversation::OutputFilterResult result = getOutputFilter()->execUnban(mask,channel); queue(result.toServer); } void Server::requestDccSend() { requestDccSend(QString()); } void Server::sendURIs(const QList& uris, const QString& nick) { foreach(const QUrl &uri, uris) addDccSend(nick, uri); } void Server::requestDccSend(const QString &a_recipient) { QString recipient(a_recipient); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if(!recipient.isEmpty()) { QPointer dlg = new DccFileDialog (getViewContainer()->getWindow()); //DccFileDialog fileDialog(KUrl(), QString(), getViewContainer()->getWindow()); QList fileURLs = dlg->getOpenUrls( QUrl(), QString(), i18n("Select File(s) to Send to %1", recipient) ); QList::const_iterator it; for ( it = fileURLs.constBegin() ; it != fileURLs.constEnd() ; ++it ) { addDccSend( recipient, *it, dlg->passiveSend()); } delete dlg; } } void Server::slotNewDccTransferItemQueued(DCC::Transfer* transfer) { if (transfer->getConnectionId() == connectionId() ) { qDebug() << "connecting slots for " << transfer->getFileName() << " [" << transfer->getType() << "]"; if ( transfer->getType() == DCC::Transfer::Receive ) { connect( transfer, SIGNAL(done(Konversation::DCC::Transfer*)), this, SLOT(dccGetDone(Konversation::DCC::Transfer*)) ); connect( transfer, SIGNAL(statusChanged(Konversation::DCC::Transfer*,int,int)), this, SLOT(dccStatusChanged(Konversation::DCC::Transfer*,int,int)) ); } else { connect( transfer, SIGNAL(done(Konversation::DCC::Transfer*)), this, SLOT(dccSendDone(Konversation::DCC::Transfer*)) ); connect( transfer, SIGNAL(statusChanged(Konversation::DCC::Transfer*,int,int)), this, SLOT(dccStatusChanged(Konversation::DCC::Transfer*,int,int)) ); } } } void Server::addDccSend(const QString &recipient, QUrl fileURL, bool passive, const QString &altFileName, quint64 fileSize) { if (!fileURL.isValid()) { return; } // We already checked that the file exists in output filter / requestDccSend() resp. DCC::TransferSend* newDcc = Application::instance()->getDccTransferManager()->newUpload(); newDcc->setConnectionId(connectionId()); newDcc->setPartnerNick(recipient); newDcc->setFileURL(fileURL); newDcc->setReverse(passive); if (!altFileName.isEmpty()) newDcc->setFileName(altFileName); if (fileSize != 0) newDcc->setFileSize(fileSize); emit addDccPanel(); if (newDcc->queue()) newDcc->start(); } QString Server::recoverDccFileName(const QStringList & dccArguments, int offset) const { QString fileName; if(dccArguments.count() > offset + 1) { qDebug() << "recover filename"; const int argumentOffsetSize = dccArguments.size() - offset; for (int i = 0; i < argumentOffsetSize; ++i) { fileName += dccArguments.at(i); //if not last element, append a space if (i < (argumentOffsetSize - 1)) { fileName += QLatin1Char(' '); } } } else { fileName = dccArguments.at(0); } return cleanDccFileName(fileName); } QString Server::cleanDccFileName(const QString& filename) const { QString cleanFileName = filename; //we want a clean filename to get rid of the mass """filename""" //NOTE: if a filename starts really with a ", it is escaped -> \" (2 chars) // but most clients don't support that and just replace it with a _ while (cleanFileName.startsWith(QLatin1Char('\"')) && cleanFileName.endsWith(QLatin1Char('\"'))) { cleanFileName = cleanFileName.mid(1, cleanFileName.length() - 2); } return cleanFileName; } quint16 Server::stringToPort(const QString &port, bool *ok) { bool toUintOk = false; uint uPort32 = port.toUInt(&toUintOk); // ports over 65535 are invalid, someone sends us bad data if (!toUintOk || uPort32 > USHRT_MAX) { if (ok) { *ok = false; } } else { if (ok) { *ok = true; } } return (quint16)uPort32; } QString Server::recipientNick() const { QStringList nickList; // fill nickList with all nicks we know about foreach (Channel* lookChannel, m_channelList) { foreach (Nick* lookNick, lookChannel->getNickList()) { if (!nickList.contains(lookNick->getChannelNick()->getNickname())) nickList.append(lookNick->getChannelNick()->getNickname()); } } // add Queries as well, but don't insert duplicates foreach (Query* lookQuery, m_queryList) { if(!nickList.contains(lookQuery->getName())) nickList.append(lookQuery->getName()); } QStringListModel model; model.setStringList(nickList); return DCC::RecipientDialog::getNickname(getViewContainer()->getWindow(), &model); } void Server::addDccGet(const QString &sourceNick, const QStringList &dccArguments) { //filename ip port filesize [token] QString ip; quint16 port; QString fileName; quint64 fileSize; QString token; const int argumentSize = dccArguments.count(); bool ok = true; if (dccArguments.at(argumentSize - 3) == QStringLiteral("0")) //port==0, for passive send, ip can't be 0 { //filename ip port(0) filesize token fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //-1 index, -1 token, -1 port, -1 filesize port = 0; fileSize = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token token = dccArguments.at(argumentSize - 1); //-1 index } else { //filename ip port filesize ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 3) ); //-1 index, -1 filesize fileName = recoverDccFileName(dccArguments, 3); //ip port filesize fileSize = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index port = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize } if (!ok) { appendMessageToFrontmost(i18n("Error"), i18nc("%1=nickname","Received invalid DCC SEND request from %1.", sourceNick)); return; } DCC::TransferRecv* newDcc = Application::instance()->getDccTransferManager()->newDownload(); newDcc->setConnectionId(connectionId()); newDcc->setPartnerNick(sourceNick); newDcc->setPartnerIp(ip); newDcc->setPartnerPort(port); newDcc->setFileName(fileName); newDcc->setFileSize(fileSize); // Reverse DCC if (!token.isEmpty()) { newDcc->setReverse(true, token); } qDebug() << "ip: " << ip; qDebug() << "port: " << port; qDebug() << "filename: " << fileName; qDebug() << "filesize: " << fileSize; qDebug() << "token: " << token; //emit after data was set emit addDccPanel(); if ( newDcc->queue() ) { appendMessageToFrontmost( i18n( "DCC" ), i18n( "%1 offers to send you \"%2\" (%3)...", newDcc->getPartnerNick(), fileName, ( newDcc->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( newDcc->getFileSize() ) ) ); if (Preferences::self()->dccAutoGet()) newDcc->start(); } } void Server::addDccChat(const QString& sourceNick, const QStringList& dccArguments) { //chat ip port [token] QString ip; quint16 port = 0; QString token; bool reverse = false; const int argumentSize = dccArguments.count(); bool ok = true; QString extension; extension = dccArguments.at(0); ip = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1)); if (argumentSize == 3) { //extension ip port port = stringToPort(dccArguments.at(2), &ok); } else if (argumentSize == 4) { //extension ip port(0) token token = dccArguments.at(3); reverse = true; } if (!ok) { appendMessageToFrontmost(i18n("Error"), i18nc("%1=nickname","Received invalid DCC CHAT request from %1.", sourceNick)); return; } DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(sourceNick); newChat->setOwnNick(getNickname()); qDebug() << "ip: " << ip; qDebug() << "port: " << port; qDebug() << "token: " << token; qDebug() << "extension: " << extension; newChat->setPartnerIp(ip); newChat->setPartnerPort(port); newChat->setReverse(reverse, token); newChat->setSelfOpened(false); newChat->setExtension(extension); emit addDccChat(newChat); newChat->start(); } void Server::openDccChat(const QString& nickname) { qDebug(); QString recipient(nickname); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if (!recipient.isEmpty()) { DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(recipient); newChat->setOwnNick(getNickname()); newChat->setSelfOpened(true); emit addDccChat(newChat); newChat->start(); } } void Server::openDccWBoard(const QString& nickname) { qDebug(); QString recipient(nickname); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if (!recipient.isEmpty()) { DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(recipient); newChat->setOwnNick(getNickname()); // Set extension before emiting addDccChat newChat->setExtension(DCC::Chat::Whiteboard); newChat->setSelfOpened(true); emit addDccChat(newChat); newChat->start(); } } void Server::requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort) { Konversation::OutputFilterResult result = getOutputFilter()->requestDccChat(partnerNick, extension, numericalOwnIp,ownPort); queue(result.toServer); } void Server::acceptDccGet(const QString& nick, const QString& file) { Application::instance()->getDccTransferManager()->acceptDccGet(m_connectionId, nick, file); } void Server::dccSendRequest(const QString &partner, const QString &fileName, const QString &address, quint16 port, quint64 size) { Konversation::OutputFilterResult result = getOutputFilter()->sendRequest(partner,fileName,address,port,size); queue(result.toServer); appendMessageToFrontmost( i18n( "DCC" ), i18n( "Asking %1 to accept upload of \"%2\" (%3)...", partner, cleanDccFileName(fileName), ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) ); } void Server::dccPassiveSendRequest(const QString& recipient,const QString& fileName,const QString& address,quint64 size,const QString& token) { Konversation::OutputFilterResult result = getOutputFilter()->passiveSendRequest(recipient,fileName,address,size,token); queue(result.toServer); appendMessageToFrontmost( i18n( "DCC" ), i18n( "Asking %1 to accept passive upload of \"%2\" (%3)...", recipient, cleanDccFileName(fileName), ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) ); } void Server::dccPassiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token) { Konversation::OutputFilterResult result = getOutputFilter()->passiveChatRequest(recipient, extension, address, token); queue(result.toServer); appendMessageToFrontmost(i18n("DCC"), i18nc("%1=name, %2=dcc extension, chat or wboard for example","Asking %1 to accept %2...", recipient, extension)); } void Server::dccPassiveResumeGetRequest(const QString& sender,const QString& fileName,quint16 port,KIO::filesize_t startAt,const QString &token) { Konversation::OutputFilterResult result = getOutputFilter()->resumePassiveRequest(sender,fileName,port,startAt,token);; queue(result.toServer); } void Server::dccResumeGetRequest(const QString &sender, const QString &fileName, quint16 port, KIO::filesize_t startAt) { Konversation::OutputFilterResult result = getOutputFilter()->resumeRequest(sender,fileName,port,startAt);; queue(result.toServer); } void Server::dccReverseSendAck(const QString& partnerNick,const QString& fileName,const QString& ownAddress,quint16 ownPort,quint64 size,const QString& reverseToken) { Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveSendRequest(partnerNick,fileName,ownAddress,ownPort,size,reverseToken); queue(result.toServer); } void Server::dccReverseChatAck(const QString& partnerNick, const QString& extension, const QString& ownAddress, quint16 ownPort, const QString& reverseToken) { Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveChatRequest(partnerNick, extension, ownAddress, ownPort, reverseToken); queue(result.toServer); } void Server::dccRejectSend(const QString& partnerNick, const QString& fileName) { Konversation::OutputFilterResult result = getOutputFilter()->rejectDccSend(partnerNick,fileName); queue(result.toServer); } void Server::dccRejectChat(const QString& partnerNick, const QString& extension) { Konversation::OutputFilterResult result = getOutputFilter()->rejectDccChat(partnerNick, extension); queue(result.toServer); } void Server::startReverseDccChat(const QString &sourceNick, const QStringList &dccArguments) { qDebug(); DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool ok = true; QString partnerIP = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1)); quint16 port = stringToPort(dccArguments.at(2), &ok); QString token = dccArguments.at(3); qDebug() << "ip: " << partnerIP; qDebug() << "port: " << port; qDebug() << "token: " << token; if (!ok || dtm->startReverseChat(connectionId(), sourceNick, partnerIP, port, token) == nullptr) { // DTM could not find a matched item appendMessageToFrontmost(i18n("Error"), i18nc("%1 = nickname", "Received invalid passive DCC chat acceptance message from %1.", sourceNick)); } } void Server::startReverseDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments) { qDebug(); DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool ok = true; const int argumentSize = dccArguments.size(); QString partnerIP = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //dccArguments[1] ) ); quint16 port = stringToPort(dccArguments.at(argumentSize - 3), &ok); QString token = dccArguments.at(argumentSize - 1); quint64 fileSize = dccArguments.at(argumentSize - 2).toULongLong(); QString fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token qDebug() << "ip: " << partnerIP; qDebug() << "port: " << port; qDebug() << "filename: " << fileName; qDebug() << "filesize: " << fileSize; qDebug() << "token: " << token; if (!ok || dtm->startReverseSending(connectionId(), sourceNick, fileName, // filename partnerIP, // partner IP port, // partner port fileSize, // filesize token // Reverse DCC token ) == nullptr) { // DTM could not find a matched item appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid passive DCC send acceptance message for \"%1\" from %2.", fileName, sourceNick)); } } void Server::resumeDccGetTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); //filename port position [token] QString fileName; quint64 position; quint16 ownPort; bool ok = true; const int argumentSize = dccArguments.count(); if (dccArguments.at(argumentSize - 3) == QStringLiteral("0")) //-1 index, -1 token, -1 pos { fileName = recoverDccFileName(dccArguments, 3); //port position token ownPort = 0; position = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token } else { fileName = recoverDccFileName(dccArguments, 2); //port position ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 pos position = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index } //do we need the token here? DCC::TransferRecv* dccTransfer = nullptr; if (ok) { dccTransfer = dtm->resumeDownload(connectionId(), sourceNick, fileName, ownPort, position); } if (dccTransfer) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender, %3 = percentage of file size, %4 = file size", "Resuming download of \"%1\" from %2 starting at %3% of %4...", fileName, sourceNick, QString::number( dccTransfer->getProgress()), (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize()))); } else { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid resume acceptance message for \"%1\" from %2.", fileName, sourceNick)); } } void Server::resumeDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool passiv = false; QString fileName; quint64 position; QString token; quint16 ownPort; bool ok = true; const int argumentSize = dccArguments.count(); //filename port filepos [token] if (dccArguments.at( argumentSize - 3) == QStringLiteral("0")) { //filename port filepos token passiv = true; ownPort = 0; token = dccArguments.at( argumentSize - 1); // -1 index position = dccArguments.at( argumentSize - 2).toULongLong(); // -1 index, -1 token fileName = recoverDccFileName(dccArguments, 3); //port filepos token } else { //filename port filepos ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize position = dccArguments.at( argumentSize - 1).toULongLong(); // -1 index fileName = recoverDccFileName(dccArguments, 2); //port filepos } DCC::TransferSend* dccTransfer = nullptr; if (ok) { dccTransfer = dtm->resumeUpload(connectionId(), sourceNick, fileName, ownPort, position); } if (dccTransfer) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient, %3 = percentage of file size, %4 = file size", "Resuming upload of \"%1\" to %2 starting at %3% of %4...", fileName, sourceNick, QString::number(dccTransfer->getProgress()), (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize()))); // fileName can't have " here if (fileName.contains(QLatin1Char(' '))) fileName = QLatin1Char('\"')+fileName+QLatin1Char('\"'); // FIXME: this operation should be done by TransferManager Konversation::OutputFilterResult result; if (passiv) result = getOutputFilter()->acceptPassiveResumeRequest( sourceNick, fileName, ownPort, position, token ); else result = getOutputFilter()->acceptResumeRequest( sourceNick, fileName, ownPort, position ); queue( result.toServer ); } else { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid resume request for \"%1\" from %2.", fileName, sourceNick)); } } void Server::rejectDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); //filename QString fileName = recoverDccFileName(dccArguments, 0); DCC::TransferSend* dccTransfer = dtm->rejectSend(connectionId(), sourceNick, fileName); if (!dccTransfer) { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid reject request for \"%1\" from %2.", fileName, sourceNick)); } } void Server::rejectDccChat(const QString& sourceNick) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); DCC::Chat* dccChat = dtm->rejectChat(connectionId(), sourceNick); if (!dccChat) { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = nickname", "Received invalid reject request from %1.", sourceNick)); } } void Server::dccGetDone(DCC::Transfer* item) { if (!item) return; if(item->getStatus() == DCC::Transfer::Done) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender", "Download of \"%1\" from %2 finished.", item->getFileName(), item->getPartnerNick())); } else if(item->getStatus() == DCC::Transfer::Failed) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender", "Download of \"%1\" from %2 failed. Reason: %3.", item->getFileName(), item->getPartnerNick(), item->getStatusDetail())); } } void Server::dccSendDone(DCC::Transfer* item) { if (!item) return; if(item->getStatus() == DCC::Transfer::Done) appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient", "Upload of \"%1\" to %2 finished.", item->getFileName(), item->getPartnerNick())); else if(item->getStatus() == DCC::Transfer::Failed) appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient", "Upload of \"%1\" to %2 failed. Reason: %3.", item->getFileName(), item->getPartnerNick(), item->getStatusDetail())); } void Server::dccStatusChanged(DCC::Transfer *item, int newStatus, int oldStatus) { if(!item) return; if ( item->getType() == DCC::Transfer::Send ) { // when resuming, a message about the receiver's acceptance has been shown already, so suppress this message if ( newStatus == DCC::Transfer::Transferring && oldStatus == DCC::Transfer::WaitingRemote && !item->isResumed() ) appendMessageToFrontmost( i18n( "DCC" ), i18nc( "%1 = file name, %2 nickname of recipient", "Sending \"%1\" to %2...", item->getFileName(), item->getPartnerNick() ) ); } else // type == Receive { if ( newStatus == DCC::Transfer::Transferring && !item->isResumed() ) { appendMessageToFrontmost( i18n( "DCC" ), i18nc( "%1 = file name, %2 = file size, %3 = nickname of sender", "Downloading \"%1\" (%2) from %3...", item->getFileName(), ( item->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( item->getFileSize() ), item->getPartnerNick() ) ); } } } void Server::removeQuery(Query* query) { m_queryList.removeOne(query); query->deleteLater(); } void Server::sendJoinCommand(const QString& name, const QString& password) { Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), Preferences::self()->commandChar() + QStringLiteral("JOIN ") + name + QLatin1Char(' ') + password, QString()); queue(result.toServer); } Channel* Server::joinChannel(const QString& name, const QString& hostmask, const QHash &messageTags) { // (re-)join channel, open a new panel if needed Channel* channel = getChannelByName(name); if (!channel) { channel=getViewContainer()->addChannel(this,name); Q_ASSERT(channel); channel->setNickname(getNickname()); channel->indicateAway(m_away); if (getServerGroup()) { Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(name); channel->setNotificationsEnabled(channelSettings.enableNotifications()); getServerGroup()->appendChannelHistory(channelSettings); } m_channelList.append(channel); m_loweredChannelNameHash.insert(channel->getName().toLower(), channel); connect(channel,SIGNAL (sendFile()),this,SLOT (requestDccSend()) ); connect(this, SIGNAL(nicknameChanged(QString)), channel, SLOT(setNickname(QString))); } // Move channel from unjoined (if present) to joined list and add our own nickname to the joined list. ChannelNickPtr channelNick = addNickToJoinedChannelsList(name, getNickname()); if ((channelNick->getHostmask() != hostmask ) && !hostmask.isEmpty()) { NickInfoPtr nickInfo = channelNick->getNickInfo(); nickInfo->setHostmask(hostmask); } channel->joinNickname(channelNick, messageTags); return channel; } void Server::removeChannel(Channel* channel) { // Update NickInfo. removeJoinedChannel(channel->getName()); if (getServerGroup()) { Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(channel->getName()); channelSettings.setNotificationsEnabled(channel->notificationsEnabled()); getServerGroup()->appendChannelHistory(channelSettings); } m_channelList.removeOne(channel); m_loweredChannelNameHash.remove(channel->getName().toLower()); if (!isConnected()) updateAutoJoin(); } void Server::updateChannelMode(const QString &updater, const QString &channelName, char mode, bool plus, const QString ¶meter, const QHash &messageTags) { Channel* channel=getChannelByName(channelName); if(channel) //Let the channel be verbose to the screen about the change, and update channelNick channel->updateMode(updater, mode, plus, parameter, messageTags); // TODO: What is mode character for owner? // Answer from JOHNFLUX - I think that admin is the same as owner. Channel.h has owner as "a" // "q" is the likely answer.. UnrealIRCd and euIRCd use it. // TODO these need to become dynamic QString userModes=QStringLiteral("vhoqa"); // voice halfop op owner admin int modePos = userModes.indexOf(QLatin1Char(mode)); if (modePos > 0) { ChannelNickPtr updateeNick = getChannelNick(channelName, parameter); if(!updateeNick) { /* if(parameter.isEmpty()) { qDebug() << "in updateChannelMode, a nick with no-name has had their mode '" << mode << "' changed to (" <setMode(mode, plus); // Note that channel will be moved to joined list if necessary. addNickToJoinedChannelsList(channelName, parameter); } // Update channel ban list. if (mode == 'b') { if (plus) { addBan(channelName, QString(QStringLiteral("%1 %2 %3")).arg(parameter).arg(updater).arg(QDateTime::currentDateTime().toTime_t())); } else { removeBan(channelName, parameter); } } } void Server::updateChannelModeWidgets(const QString &channelName, char mode, const QString ¶meter) { Channel* channel=getChannelByName(channelName); if(channel) channel->updateModeWidgets(mode,true,parameter); } Channel* Server::getChannelByName(const QString& name) { if (name.isEmpty()) { return nullptr; } // Convert wanted channel name to lowercase QString wanted = name.toLower(); QRegularExpressionMatch p = m_targetMatcher.match(wanted); int index = p.capturedStart(2); if (index >= 0) { wanted = wanted.mid(index); if (m_loweredChannelNameHash.contains(wanted)) return m_loweredChannelNameHash.value(wanted); } return nullptr; } Query* Server::getQueryByName(const QString& name) { // Convert wanted query name to lowercase QString wanted = name.toLower(); // Traverse through list to find the query with "name" foreach (Query* lookQuery, m_queryList) { if(lookQuery->getName().toLower()==wanted) return lookQuery; } // No query by that name found? Must be a new query request. Return 0 return nullptr; } ChatWindow* Server::getChannelOrQueryByName(const QString& name) { ChatWindow* window = getChannelByName(name); if (!window) window = getQueryByName(name); return window; } void Server::queueNicks(const QString& channelName, const QStringList& nicknameList) { Channel* channel = getChannelByName(channelName); if (channel) channel->queueNicks(nicknameList); } // Adds a nickname to the joinedChannels list. // Creates new NickInfo if necessary. // If needed, moves the channel from the unjoined list to the joined list. // Returns the NickInfo for the nickname. ChannelNickPtr Server::addNickToJoinedChannelsList(const QString& channelName, const QString& nickname) { bool doChannelJoinedSignal = false; bool doWatchedNickChangedSignal = false; bool doChannelMembersChangedSignal = false; QString lcNickname(nickname.toLower()); // Create NickInfo if not already created. NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); doWatchedNickChangedSignal = isWatchedNick(nickname); } // if nickinfo already exists update nickname, in case we created the nickinfo based // on e.g. an incorrectly capitalized ISON request else nickInfo->setNickname(nickname); // Move the channel from unjoined list (if present) to joined list. QString lcChannelName = channelName.toLower(); ChannelNickMap *channel; if (m_unjoinedChannels.contains(lcChannelName)) { channel = m_unjoinedChannels[lcChannelName]; m_unjoinedChannels.remove(lcChannelName); m_joinedChannels.insert(lcChannelName, channel); doChannelJoinedSignal = true; } else { // Create a new list in the joined channels if not already present. if (!m_joinedChannels.contains(lcChannelName)) { channel = new ChannelNickMap; m_joinedChannels.insert(lcChannelName, channel); doChannelJoinedSignal = true; } else channel = m_joinedChannels[lcChannelName]; } // Add NickInfo to channel list if not already in the list. ChannelNickPtr channelNick; if (!channel->contains(lcNickname)) { channelNick = new ChannelNick(nickInfo, lcChannelName); Q_ASSERT(channelNick); channel->insert(lcNickname, channelNick); doChannelMembersChangedSignal = true; } channelNick = (*channel)[lcNickname]; Q_ASSERT(channelNick); //Since we just added it if it didn't exist, it should be guaranteed to exist now if (doWatchedNickChangedSignal) emit watchedNickChanged(this, nickname, true); if (doChannelJoinedSignal) emit channelJoinedOrUnjoined(this, channelName, true); if (doChannelMembersChangedSignal) emit channelMembersChanged(this, channelName, true, false, nickname); return channelNick; } // Adds a nickname to the unjoinedChannels list. // Creates new NickInfo if necessary. // If needed, moves the channel from the joined list to the unjoined list. // If mode != 99 sets the mode for this nick in this channel. // Returns the NickInfo for the nickname. ChannelNickPtr Server::addNickToUnjoinedChannelsList(const QString& channelName, const QString& nickname) { bool doChannelUnjoinedSignal = false; bool doWatchedNickChangedSignal = false; bool doChannelMembersChangedSignal = false; QString lcNickname(nickname.toLower()); // Create NickInfo if not already created. NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); doWatchedNickChangedSignal = isWatchedNick(nickname); } // Move the channel from joined list (if present) to unjoined list. QString lcChannelName = channelName.toLower(); ChannelNickMap *channel; if (m_joinedChannels.contains(lcChannelName)) { channel = m_joinedChannels[lcChannelName]; m_joinedChannels.remove(lcChannelName); m_unjoinedChannels.insert(lcChannelName, channel); doChannelUnjoinedSignal = true; } else { // Create a new list in the unjoined channels if not already present. if (!m_unjoinedChannels.contains(lcChannelName)) { channel = new ChannelNickMap; m_unjoinedChannels.insert(lcChannelName, channel); doChannelUnjoinedSignal = true; } else channel = m_unjoinedChannels[lcChannelName]; } // Add NickInfo to unjoinedChannels list if not already in the list. ChannelNickPtr channelNick; if (!channel->contains(lcNickname)) { channelNick = new ChannelNick(nickInfo, lcChannelName); channel->insert(lcNickname, channelNick); doChannelMembersChangedSignal = true; } channelNick = (*channel)[lcNickname]; // Set the mode for the nick in this channel. if (doWatchedNickChangedSignal) emit watchedNickChanged(this, nickname, true); if (doChannelUnjoinedSignal) emit channelJoinedOrUnjoined(this, channelName, false); if (doChannelMembersChangedSignal) emit channelMembersChanged(this, channelName, false, false, nickname); return channelNick; } /** * If not already online, changes a nick to the online state by creating * a NickInfo for it and emits various signals and messages for it. * This method should only be called for nicks on the watch list. * @param nickname The nickname that is online. * @return Pointer to NickInfo for nick. */ NickInfoPtr Server::setWatchedNickOnline(const QString& nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { QString lcNickname(nickname.toLower()); nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); } emit watchedNickChanged(this, nickname, true); appendMessageToFrontmost(i18nc("Message type", "Notify"), i18n("%1 is online (%2).", nickname, getServerName()), QHash(), getStatusView()); Application::instance()->notificationHandler()->nickOnline(getStatusView(), nickname); nickInfo->setPrintedOnline(true); return nickInfo; } void Server::setWatchedNickOffline(const QString& nickname, const NickInfoPtr nickInfo) { Q_UNUSED(nickInfo) emit watchedNickChanged(this, nickname, false); appendMessageToFrontmost(i18nc("Message type", "Notify"), i18n("%1 went offline (%2).", nickname, getServerName()), QHash(), getStatusView()); Application::instance()->notificationHandler()->nickOffline(getStatusView(), nickname); } bool Server::setNickOffline(const QString& nickname) { QString lcNickname(nickname.toLower()); NickInfoPtr nickInfo = getNickInfo(lcNickname); bool wasOnline = nickInfo ? nickInfo->getPrintedOnline() : false; if (wasOnline) { // Delete from query list, if present. if (m_queryNicks.contains(lcNickname)) m_queryNicks.remove(lcNickname); // Delete the nickname from all channels (joined or unjoined). QStringList nickChannels = getNickChannels(lcNickname); QStringList::iterator itEnd = nickChannels.end(); for(QStringList::iterator it = nickChannels.begin(); it != itEnd; ++it) { QString channel = (*it); removeChannelNick(channel, lcNickname); } // Delete NickInfo. if (m_allNicks.contains(lcNickname)) m_allNicks.remove(lcNickname); // If the nick was in the watch list, emit various signals and messages. if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, nickInfo); nickInfo->setPrintedOnline(false); } return (nickInfo != nullptr); } /** * If nickname is no longer on any channel list, or the query list, delete it altogether. * Call this routine only if the nick is not on the notify list or is on the notify * list but is known to be offline. * @param nickname The nickname to be deleted. Case insensitive. * @return True if the nickname is deleted. */ bool Server::deleteNickIfUnlisted(const QString &nickname) { QString lcNickname(nickname.toLower()); // Don't delete our own nickinfo. if (lcNickname == loweredNickname()) return false; if (!m_queryNicks.contains(lcNickname)) { QStringList nickChannels = getNickChannels(nickname); if (nickChannels.isEmpty()) { m_allNicks.remove(lcNickname); return true; } } return false; } /** * Remove nickname from a channel (on joined or unjoined lists). * @param channelName The channel name. Case insensitive. * @param nickname The nickname. Case insensitive. */ void Server::removeChannelNick(const QString& channelName, const QString& nickname) { bool doSignal = false; bool joined = false; QString lcChannelName = channelName.toLower(); QString lcNickname = nickname.toLower(); ChannelNickMap *channel; if (m_joinedChannels.contains(lcChannelName)) { channel = m_joinedChannels[lcChannelName]; if (channel->contains(lcNickname)) { channel->remove(lcNickname); doSignal = true; joined = true; // Note: Channel should not be empty because user's own nick should still be // in it, so do not need to delete empty channel here. } else { qDebug() << "Error: Tried to remove nickname=" << nickname << " from joined channel=" << channelName; } } else { if (m_unjoinedChannels.contains(lcChannelName)) { channel = m_unjoinedChannels[lcChannelName]; if (channel->contains(lcNickname)) { channel->remove(lcNickname); doSignal = true; joined = false; // If channel is now empty, delete it. // Caution: Any iterators across unjoinedChannels will be come invalid here. if (channel->isEmpty()) m_unjoinedChannels.remove(lcChannelName); } else { qDebug() << "Error: Tried to remove nickname=" << nickname << " from unjoined channel=" << channelName; } } } if (doSignal) emit channelMembersChanged(this, channelName, joined, true, nickname); } QStringList Server::getWatchList() { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::notifyListByGroupId(getServerGroup()->id()); else return QStringList(); if (m_serverISON) return m_serverISON->getWatchList(); else return QStringList(); } QStringList Server::getISONList() { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::notifyListByGroupId(getServerGroup()->id()); else return QStringList(); if (m_serverISON) return m_serverISON->getISONList(); else return QStringList(); } QString Server::getISONListString() { return getISONList().join(QStringLiteral(" ")); } /** * Return true if the given nickname is on the watch list. */ bool Server::isWatchedNick(const QString& nickname) { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::isNotify(getServerGroup()->id(), nickname); else return false; // ###### ERROR: not reached return getWatchList().contains(nickname, Qt::CaseInsensitive); } /** * Remove channel from the joined list, placing it in the unjoined list. * All the unwatched nicks are removed from the channel. If the channel becomes * empty, it is deleted. * @param channelName Name of the channel. Case sensitive. */ void Server::removeJoinedChannel(const QString& channelName) { bool doSignal = false; QStringList watchListLower = getWatchList(); QString lcChannelName = channelName.toLower(); // Move the channel nick list from the joined to unjoined lists. if (m_joinedChannels.contains(lcChannelName)) { doSignal = true; ChannelNickMap* channel = m_joinedChannels[lcChannelName]; m_joinedChannels.remove(lcChannelName); m_unjoinedChannels.insert(lcChannelName, channel); // Remove nicks not on the watch list. bool allDeleted = true; Q_ASSERT(channel); if(!channel) return; //already removed.. hmm ChannelNickMap::Iterator member; for ( member = channel->begin(); member != channel->end() ;) { QString lcNickname = member.key(); if (!watchListLower.contains(lcNickname)) { // Remove the unwatched nickname from the unjoined channel. channel->erase(member); // If the nick is no longer listed in any channels or query list, delete it altogether. deleteNickIfUnlisted(lcNickname); member = channel->begin(); } else { allDeleted = false; ++member; } } // If all were deleted, remove the channel from the unjoined list. if (allDeleted) { channel = m_unjoinedChannels[lcChannelName]; m_unjoinedChannels.remove(lcChannelName); delete channel; // recover memory! } } if (doSignal) emit channelJoinedOrUnjoined(this, channelName, false); } // Renames a nickname in all NickInfo lists. // Returns pointer to the NickInfo object or 0 if nick not found. void Server::renameNickInfo(NickInfoPtr nickInfo, const QString& newname) { if (nickInfo) { // Get existing lowercase nickname and rename nickname in the NickInfo object. QString lcNickname(nickInfo->loweredNickname()); nickInfo->setNickname(newname); QString lcNewname(newname.toLower()); // Rename the key in m_allNicks list. m_allNicks.remove(lcNickname); m_allNicks.insert(lcNewname, nickInfo); // Rename key in the joined and unjoined lists. QStringList nickChannels = getNickChannels(lcNickname); QStringList::iterator itEnd = nickChannels.end(); for(QStringList::iterator it = nickChannels.begin(); it != itEnd; ++it) { const ChannelNickMap *channel = getChannelMembers(*it); Q_ASSERT(channel); ChannelNickPtr member = (*channel)[lcNickname]; Q_ASSERT(member); const_cast(channel)->remove(lcNickname); const_cast(channel)->insert(lcNewname, member); } // Rename key in Query list. if (m_queryNicks.contains(lcNickname)) { m_queryNicks.remove(lcNickname); m_queryNicks.insert(lcNewname, nickInfo); } } else { qDebug() << "was called for newname='" << newname << "' but nickInfo is null"; } } Channel* Server::nickJoinsChannel(const QString &channelName, const QString &nickname, const QString &hostmask, const QString &account, const QString &realName, const QHash &messageTags) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { // Update NickInfo. ChannelNickPtr channelNick = addNickToJoinedChannelsList(channelName, nickname); NickInfoPtr nickInfo = channelNick->getNickInfo(); if ((nickInfo->getHostmask() != hostmask) && !hostmask.isEmpty()) { nickInfo->setHostmask(hostmask); } if (!account.isEmpty()) { nickInfo->setAccount(account); } if (!realName.isEmpty()) { nickInfo->setRealName(realName); } outChannel->joinNickname(channelNick, messageTags); } return outChannel; } void Server::addHostmaskToNick(const QString& sourceNick, const QString& sourceHostmask) { // Update NickInfo. NickInfoPtr nickInfo = getNickInfo(sourceNick); if (nickInfo) { if ((nickInfo->getHostmask() != sourceHostmask) && !sourceHostmask.isEmpty()) { nickInfo->setHostmask(sourceHostmask); } } } Channel* Server::removeNickFromChannel(const QString &channelName, const QString &nickname, const QString &reason, const QHash &messageTags, bool quit) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { outChannel->flushNickQueue(); ChannelNickPtr channelNick = getChannelNick(channelName, nickname); if(channelNick) { outChannel->removeNick(channelNick,reason,quit, messageTags); } } // Remove the nick from the channel. removeChannelNick(channelName, nickname); // If not listed in any channel, and not on query list, delete the NickInfo, // but only if not on the notify list. ISON replies will take care of deleting // the NickInfo, if on the notify list. if (!isWatchedNick(nickname)) { QString nicky = nickname; deleteNickIfUnlisted(nicky); } return outChannel; } void Server::nickWasKickedFromChannel(const QString &channelName, const QString &nickname, const QString &kicker, const QString &reason, const QHash &messageTags) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { outChannel->flushNickQueue(); ChannelNickPtr channelNick = getChannelNick(channelName, nickname); if(channelNick) { outChannel->kickNick(channelNick, kicker, reason, messageTags); // Tell Nickinfo removeChannelNick(channelName,nickname); } } } void Server::removeNickFromServer(const QString &nickname,const QString &reason, const QHash &messageTags) { foreach (Channel* channel, m_channelList) { channel->flushNickQueue(); // Check if nick is in this channel or not. if(channel->getNickByName(nickname)) removeNickFromChannel(channel->getName(), nickname, reason, messageTags, true); } Query* query = getQueryByName(nickname); if (query) query->quitNick(reason, messageTags); // Delete the nick from all channels and then delete the nickinfo, // emitting signal if on the watch list. setNickOffline(nickname); } void Server::renameNick(const QString &nickname, const QString &newNick, const QHash &messageTags) { if(nickname.isEmpty() || newNick.isEmpty()) { qDebug() << "called with empty strings! Trying to rename '" << nickname << "' to '" << newNick << "'"; return; } // If this was our own nickchange, tell our server object about it if (nickname == getNickname()) setNickname(newNick); //Actually do the rename. NickInfoPtr nickInfo = getNickInfo(nickname); if(!nickInfo) { qDebug() << "called for nickname '" << nickname << "' to '" << newNick << "' but getNickInfo('" << nickname << "') returned no results."; } else { renameNickInfo(nickInfo, newNick); //The rest of the code below allows the channels to echo to the user to tell them that the nick has changed. // Rename the nick in every channel they are in foreach (Channel* channel, m_channelList) { channel->flushNickQueue(); // All we do is notify that the nick has been renamed.. we haven't actually renamed it yet if (channel->getNickByName(nickname)) channel->nickRenamed(nickname, *nickInfo, messageTags); } //Watched nicknames stuff if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, NickInfoPtr()); } // We had an encrypt conversation with the user that changed his nick, lets copy the key to the new nick and remove the old nick #ifdef HAVE_QCA2 QByteArray userKey = getKeyForRecipient(nickname); if (!userKey.isEmpty()) { setKeyForRecipient(newNick, userKey); m_keyHash.remove(nickname.toLower()); } #endif } void Server::userhost(const QString& nick,const QString& hostmask,bool away,bool /* ircOp */) { addHostmaskToNick(nick, hostmask); // remember my IP for DCC things // myself if (m_ownIpByUserhost.isEmpty() && nick == getNickname()) { QString myhost = hostmask.section(QLatin1Char('@'), 1); // Use async lookup else you will be blocking GUI badly QHostInfo::lookupHost(myhost, this, SLOT(gotOwnResolvedHostByUserhost(QHostInfo))); } NickInfoPtr nickInfo = getNickInfo(nick); if (nickInfo) { if (nickInfo->isAway() != away) { nickInfo->setAway(away); } } } void Server::gotOwnResolvedHostByUserhost(const QHostInfo& res) { if ( res.error() == QHostInfo::NoError && !res.addresses().isEmpty() ) m_ownIpByUserhost = res.addresses().first().toString(); else qDebug() << "Got error: " << res.errorString(); } void Server::appendServerMessageToChannel(const QString& channel,const QString& type,const QString& message, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if (outChannel) outChannel->appendServerMessage(type, message, messageTags); } void Server::appendCommandMessageToChannel(const QString& channel, const QString& command, const QString& message, const QHash &messageTags, bool highlight, bool parseURL) { Channel* outChannel = getChannelByName(channel); if (outChannel) { outChannel->appendCommandMessage(command, message, messageTags, parseURL, !highlight); } else { appendStatusMessage(command, QString(QStringLiteral("%1 %2")).arg(channel).arg(message), messageTags); } } void Server::appendStatusMessage(const QString& type, const QString& message, const QHash &messageTags) { getStatusView()->appendServerMessage(type, message, messageTags); } void Server::appendMessageToFrontmost(const QString& type, const QString& message, const QHash &messageTags, bool parseURL) { getViewContainer()->appendToFrontmost(type, message, getStatusView(), messageTags, parseURL); } void Server::setNickname(const QString &newNickname) { m_nickname = newNickname; m_loweredNickname = newNickname.toLower(); if (!m_nickListModel->stringList().contains(newNickname)) { m_nickListModel->insertRows(m_nickListModel->rowCount(), 1); m_nickListModel->setData(m_nickListModel->index(m_nickListModel->rowCount() -1 , 0), newNickname, Qt::DisplayRole); } emit nicknameChanged(newNickname); } void Server::setChannelTopic(const QString &channel, const QString &newTopic, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if(outChannel) { // encoding stuff is done in send() outChannel->setTopic(newTopic, messageTags); } } // Overloaded void Server::setChannelTopic(const QString& nickname, const QString &channel, const QString &newTopic, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if(outChannel) { // encoding stuff is done in send() outChannel->setTopic(nickname, newTopic, messageTags); } } void Server::setTopicAuthor(const QString& channel, const QString& author, QDateTime time) { Channel* outChannel = getChannelByName(channel); if(outChannel) outChannel->setTopicAuthor(author, time); } void Server::endOfWho(const QString& target) { Channel* channel = getChannelByName(target); if(channel) channel->scheduleAutoWho(); } void Server::endOfNames(const QString& target) { Channel* channel = getChannelByName(target); if(channel) channel->endOfNames(); } bool Server::isNickname(const QString &compare) const { return (m_nickname == compare); } QString Server::getNickname() const { return m_nickname; } QString Server::loweredNickname() const { return m_loweredNickname; } QString Server::parseWildcards(const QString& toParse, ChatWindow* context, QStringList nicks) { QString inputLineText; if (context && context->getInputBar()) inputLineText = context->getInputBar()->toPlainText(); if (!context) return parseWildcards(toParse, getNickname(), QString(), QString(), QString(), QString()); else if (context->getType() == ChatWindow::Channel) { Channel* channel = qobject_cast(context); return parseWildcards(toParse, getNickname(), context->getName(), channel->getPassword(), nicks.count() ? nicks : channel->getSelectedNickList(), inputLineText); } else if (context->getType() == ChatWindow::Query) return parseWildcards(toParse, getNickname(), context->getName(), QString(), context->getName(), inputLineText); return parseWildcards(toParse, getNickname(), context->getName(), QString(), QString(), inputLineText); } QString Server::parseWildcards(const QString& toParse, const QString& sender, const QString& channelName, const QString& channelKey, const QString& nick, const QString& inputLineText) { return parseWildcards(toParse, sender, channelName, channelKey, nick.split(QLatin1Char(' '), QString::SkipEmptyParts), inputLineText); } QString Server::parseWildcards(const QString& toParse, const QString& sender, const QString& channelName, const QString& channelKey, const QStringList& nickList, const QString& inputLineText ) { // store the parsed version QString out; // default separator QString separator(QStringLiteral(" ")); int index = 0, found = 0; QChar toExpand; while ((found = toParse.indexOf(QLatin1Char('%'), index)) != -1) { // append part before the % - out.append(toParse.mid(index,found-index)); + out.append(toParse.midRef(index,found-index)); index = found + 1; // skip the part before, including % if (index >= (int)toParse.length()) break; // % was the last char (not valid) toExpand = toParse.at(index++); if (toExpand == QLatin1Char('s')) { found = toParse.indexOf(QLatin1Char('%'), index); if (found == -1) // no other % (not valid) break; separator = toParse.mid(index,found-index); index = found + 1; // skip separator, including % } else if (toExpand == QLatin1Char('u')) { out.append(nickList.join(separator)); } else if (toExpand == QLatin1Char('c')) { if(!channelName.isEmpty()) out.append(channelName); } else if (toExpand == QLatin1Char('o')) { out.append(sender); } else if (toExpand == QLatin1Char('k')) { if(!channelKey.isEmpty()) out.append(channelKey); } else if (toExpand == QLatin1Char('K')) { if(getConnectionSettings().server().password().isEmpty()) out.append(getConnectionSettings().server().password()); } else if (toExpand == QLatin1Char('n')) { out.append(QStringLiteral("\n")); } else if (toExpand == QLatin1Char('p')) { out.append(QStringLiteral("%")); } else if (toExpand == QLatin1Char('i')) { out.append(inputLineText); } } // append last part - out.append(toParse.mid(index,toParse.length()-index)); + out.append(toParse.midRef(index,toParse.length()-index)); return out; } void Server::sendToAllChannels(const QString &text) { // Send a message to all channels we are in foreach (Channel* channel, m_channelList) { channel->sendText(text); } } void Server::invitation(const QString& nick,const QString& channel) { if(!m_inviteDialog) { QDialogButtonBox::StandardButton buttonCode = QDialogButtonBox::Cancel; if(!InviteDialog::shouldBeShown(buttonCode)) { if (buttonCode == QDialogButtonBox::Ok) sendJoinCommand(channel); return; } m_inviteDialog = new InviteDialog (getViewContainer()->getWindow()); connect(m_inviteDialog, SIGNAL(joinChannelsRequested(QString)), this, SLOT(sendJoinCommand(QString))); } m_inviteDialog->show(); m_inviteDialog->raise(); m_inviteDialog->addInvite(nick, channel); } void Server::scriptNotFound(const QString& name) { appendMessageToFrontmost(i18n("D-Bus"),i18n("Error: Could not find script \"%1\".", name)); } void Server::scriptExecutionError(const QString& name) { appendMessageToFrontmost(i18n("D-Bus"),i18n("Error: Could not execute script \"%1\". Check file permissions.", name)); } bool Server::isAChannel(const QString &channel) const { if (channel.isEmpty()) return false; QRegularExpressionMatch x = m_targetMatcher.match(channel); int index = x.capturedStart(2); return (index >= 0 && getChannelTypes().contains(channel.at(index)) > 0); } void Server::addRawLog(bool show) { if (!m_rawLog) m_rawLog = getViewContainer()->addRawLog(this); connect(this, SIGNAL(serverOnline(bool)), m_rawLog, SLOT(serverOnline(bool))); // bring raw log to front since the main window does not do this for us if (show) emit showView(m_rawLog); } void Server::closeRawLog() { delete m_rawLog; } void Server::requestOpenChannelListPanel(const QString& filter) { getViewContainer()->openChannelList(this, filter, true); } ChannelListPanel* Server::addChannelListPanel() { if(!m_channelListPanel) { m_channelListPanel = getViewContainer()->addChannelListPanel(this); connect(&m_inputFilter, SIGNAL(endOfChannelList()), m_channelListPanel, SLOT(endOfChannelList())); connect(m_channelListPanel, SIGNAL(refreshChannelList()), this, SLOT(requestChannelList())); connect(m_channelListPanel, SIGNAL(joinChannel(QString)), this, SLOT(sendJoinCommand(QString))); connect(this, SIGNAL(serverOnline(bool)), m_channelListPanel, SLOT(serverOnline(bool))); } return m_channelListPanel; } void Server::addToChannelList(const QString& channel, int users, const QString& topic) { addChannelListPanel(); m_channelListPanel->addToChannelList(channel, users, topic); } ChannelListPanel* Server::getChannelListPanel() const { return m_channelListPanel; } void Server::closeChannelListPanel() { delete m_channelListPanel; } void Server::updateAutoJoin(Konversation::ChannelList channels) { Konversation::ChannelList tmpList; if (!channels.isEmpty()) { foreach (const ChannelSettings& cs, channels) { tmpList << cs; } } else if (m_channelList.isEmpty() && getServerGroup()) tmpList = getServerGroup()->channelList(); else { foreach (Channel* channel, m_channelList) { tmpList << channel->channelSettings(); } } if (!tmpList.isEmpty()) { setAutoJoinCommands(generateJoinCommand(tmpList)); setAutoJoin(!m_autoJoinCommands.isEmpty()); } else { m_autoJoinCommands.clear(); setAutoJoin(false); } } QStringList Server::generateJoinCommand(const Konversation::ChannelList &tmpList) { QStringList channels; QStringList passwords; QStringList joinCommands; uint length = 0; Konversation::ChannelList::const_iterator it; for (it = tmpList.constBegin(); it != tmpList.constEnd(); ++it) { QString channel = (*it).name(); // Only add the channel to the JOIN command if it has a valid channel name. if (isAChannel(channel)) { QString password = ((*it).password().isEmpty() ? QStringLiteral(".") : (*it).password()); uint currentLength = getIdentity()->getCodec()->fromUnicode(channel).length(); currentLength += getIdentity()->getCodec()->fromUnicode(password).length(); //channels.count() and passwords.count() account for the commas if (length + currentLength + 6 + channels.count() + passwords.count() >= 512) // 6: "JOIN " plus separating space between chans and pws. { while (!passwords.isEmpty() && passwords.last() == QStringLiteral(".")) passwords.pop_back(); joinCommands << QStringLiteral("JOIN ") + channels.join(QStringLiteral(",")) + QLatin1Char(' ') + passwords.join(QStringLiteral(",")); channels.clear(); passwords.clear(); length = 0; } length += currentLength; channels << channel; passwords << password; } } while (!passwords.isEmpty() && passwords.last() == QStringLiteral(".")) passwords.pop_back(); // Even if the given tmpList contained entries they might have been filtered // out by the isAChannel() check. if (!channels.isEmpty()) { joinCommands << QStringLiteral("JOIN ") + channels.join(QStringLiteral(",")) + QLatin1Char(' ') + passwords.join(QStringLiteral(",")); } return joinCommands; } ViewContainer* Server::getViewContainer() const { Application* konvApp = Application::instance(); return konvApp->getMainWindow()->getViewContainer(); } bool Server::getUseSSL() const { if ( m_socket ) return ( m_socket->encryptionMode() != KTcpSocket::UnencryptedMode ); else return false; } QString Server::getSSLInfo() const { // SSLSocket* sslsocket = dynamic_cast(m_socket); // if(sslsocket) // return sslsocket->details(); return QString(); } void Server::sendMultiServerCommand(const QString& command, const QString& parameter) { emit multiServerCommand(command, parameter); } void Server::executeMultiServerCommand(const QString& command, const QString& parameter) { if (command == QStringLiteral("msg")) sendToAllChannelsAndQueries(parameter); else sendToAllChannelsAndQueries(Preferences::self()->commandChar() + command + QLatin1Char(' ') + parameter); } void Server::sendToAllChannelsAndQueries(const QString& text) { // Send a message to all channels we are in foreach (Channel* channel, m_channelList) { channel->sendText(text); } // Send a message to all queries we are in foreach (Query* query, m_queryList) { query->sendText(text); } } void Server::requestAway(const QString& reason) { QString awayReason = reason; IdentityPtr identity = getIdentity(); if (awayReason.isEmpty() && identity) awayReason = identity->getAwayMessage(); // Fallback in case the identity has no away message set. if (awayReason.isEmpty()) awayReason = i18n("Gone away for now"); setAwayReason(awayReason); queue(QStringLiteral("AWAY :") + awayReason); } void Server::requestUnaway() { queue(QStringLiteral("AWAY")); } void Server::setAway(bool away, const QHash &messageTags) { IdentityPtr identity = getIdentity(); if (away) { if (!m_away) startAwayTimer(); m_away = true; emit awayState(true); if (identity && !identity->getAwayNickname().isEmpty() && identity->getAwayNickname() != getNickname()) { m_nonAwayNick = getNickname(); queue(QStringLiteral("NICK ") + getIdentity()->getAwayNickname()); } if (!m_awayReason.isEmpty()) appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away (reason: %1).",m_awayReason), messageTags); else appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away."), messageTags); if (identity && identity->getRunAwayCommands()) { QString message = identity->getAwayCommand(); sendToAllChannels(message.replace(QRegExp(QStringLiteral("%s"), Qt::CaseInsensitive), m_awayReason)); } if (identity && identity->getInsertRememberLineOnAway()) emit awayInsertRememberLine(this); } else { m_awayReason.clear(); emit awayState(false); if (!identity->getAwayNickname().isEmpty() && !m_nonAwayNick.isEmpty()) { queue(QStringLiteral("NICK ") + m_nonAwayNick); m_nonAwayNick.clear(); } if (m_away) { appendMessageToFrontmost(i18n("Away"), i18n("You are no longer marked as being away."), messageTags); if (identity && identity->getRunAwayCommands()) { QString message = identity->getReturnCommand(); sendToAllChannels(message.replace(QRegExp(QStringLiteral("%t"), Qt::CaseInsensitive), awayTime())); } } else appendMessageToFrontmost(i18n("Away"), i18n("You are not marked as being away."), messageTags); m_away = false; } } QString Server::awayTime() const { QString retVal; if (m_away) { int diff = QDateTime::currentDateTime().toTime_t() - m_awayTime; int num = diff / 3600; if (num < 10) retVal = QLatin1Char('0') + QString::number(num) + QLatin1Char(':'); else retVal = QString::number(num) + QLatin1Char(':'); num = (diff % 3600) / 60; if (num < 10) retVal += QLatin1Char('0'); retVal += QString::number(num) + QLatin1Char(':'); num = (diff % 3600) % 60; if (num < 10) retVal += QLatin1Char('0'); retVal += QString::number(num); } else retVal = QStringLiteral("00:00:00"); return retVal; } void Server::startAwayTimer() { m_awayTime = QDateTime::currentDateTime().toTime_t(); } void Server::enableIdentifyMsg(bool enabled) { m_identifyMsg = enabled; } bool Server::identifyMsgEnabled() { return m_identifyMsg; } void Server::addBan(const QString &channel, const QString &ban) { Channel* outChannel = getChannelByName(channel); if(outChannel) { outChannel->addBan(ban); } } void Server::removeBan(const QString &channel, const QString &ban) { Channel* outChannel = getChannelByName(channel); if(outChannel) { outChannel->removeBan(ban); } } void Server::sendPing() { //WHO ourselves once a minute in case the irc server has changed our //hostmask, such as what happens when a Freenode cloak is activated. //It might be more intelligent to only do this when there is text //in the inputbox. Kinda changes this into a "do minutely" //queue :-) QStringList ql; ql << QStringLiteral("PING LAG") + QTime::currentTime().toString(QStringLiteral("hhmmss")); getInputFilter()->setAutomaticRequest(QStringLiteral("WHO"), getNickname(), true); ql << QStringLiteral("WHO ") + getNickname(); queueList(ql, HighPriority); m_lagTime.start(); m_inputFilter.setLagMeasuring(true); m_pingResponseTimer.start(1000 /*1 sec*/); } void Server::pongReceived() { // ignore unrequested PONGs if (m_pingSendTimer.isActive()) return; m_currentLag = m_lagTime.elapsed(); m_inputFilter.setLagMeasuring(false); m_pingResponseTimer.stop(); emit serverLag(this, m_currentLag); // Send another PING in 60 seconds m_pingSendTimer.start(60000 /*60 sec*/); } void Server::updateLongPongLag() { if (isSocketConnected()) { m_currentLag = m_lagTime.elapsed(); emit tooLongLag(this, m_currentLag); // qDebug() << "Current lag: " << currentLag; if (m_currentLag > (Preferences::self()->maximumLagTime() * 1000)) m_socket->close(); } } void Server::updateEncoding() { if(getViewContainer() && getViewContainer()->getFrontView()) getViewContainer()->updateViewEncoding(getViewContainer()->getFrontView()); } #ifdef HAVE_QCA2 void Server::initKeyExchange(const QString &receiver) { Query* query; if (getQueryByName(receiver)) { query = getQueryByName(receiver); } else { NickInfoPtr nickinfo = obtainNickInfo(receiver); query = addQuery(nickinfo, true); } Konversation::Cipher* cipher = query->getCipher(); QByteArray pubKey = cipher->initKeyExchange(); if(pubKey.isEmpty()) { appendMessageToFrontmost(i18n("Error"), i18n("Failed to initiate key exchange with %1.", receiver)); } else { queue("NOTICE "+receiver+" :DH1080_INIT "+pubKey); } } void Server::parseInitKeyX(const QString &sender, const QString &remoteKey) { if (!Konversation::Cipher::isFeatureAvailable(Konversation::Cipher::DH)) { appendMessageToFrontmost(i18n("Error"), i18n("Unable to perform key exchange with %1.", sender) + ' ' + Konversation::Cipher::runtimeError()); return; } //TODO ask the user to accept without blocking Query* query; if (getQueryByName(sender)) { query = getQueryByName(sender); } else { NickInfoPtr nickinfo = obtainNickInfo(sender); query = addQuery(nickinfo, false); } Konversation::Cipher* cipher = query->getCipher(); QByteArray pubKey = cipher->parseInitKeyX(remoteKey.toLocal8Bit()); if (pubKey.isEmpty()) { appendMessageToFrontmost(i18n("Error"), i18n("Failed to parse the DH1080_INIT of %1. Key exchange failed.", sender)); } else { setKeyForRecipient(sender, cipher->key()); query->setEncryptedOutput(true); appendMessageToFrontmost(i18n("Notice"), i18n("Your key is set and your messages will now be encrypted, sending DH1080_FINISH to %1.", sender)); queue("NOTICE "+sender+" :DH1080_FINISH "+pubKey); } } void Server::parseFinishKeyX(const QString &sender, const QString &remoteKey) { Query* query; if (getQueryByName(sender)) { query = getQueryByName(sender); } else return; if (!Konversation::Cipher::isFeatureAvailable(Konversation::Cipher::DH)) { appendMessageToFrontmost(i18n("Error"), i18n("Unable to complete key exchange with %1.", sender) + ' ' + Konversation::Cipher::runtimeError()); return; } Konversation::Cipher* cipher = query->getCipher(); if (cipher->parseFinishKeyX(remoteKey.toLocal8Bit())) { setKeyForRecipient(sender,cipher->key()); query->setEncryptedOutput(true); appendMessageToFrontmost(i18n("Notice"), i18n("Successfully parsed DH1080_FINISH sent by %1. Your key is set and your messages will now be encrypted.", sender)); } else { appendMessageToFrontmost(i18n("Error"), i18n("Failed to parse DH1080_FINISH sent by %1. Key exchange failed.", sender)); } } #endif QAbstractItemModel* Server::nickListModel() const { return m_nickListModel; } void Server::startNickInfoChangedTimer() { if(!m_nickInfoChangedTimer->isActive()) m_nickInfoChangedTimer->start(); } void Server::sendNickInfoChangedSignals() { emit nickInfoChanged(); foreach(NickInfoPtr nickInfo, m_allNicks) { if(nickInfo->isChanged()) { emit nickInfoChanged(this, nickInfo); nickInfo->setChanged(false); } } } void Server::startChannelNickChangedTimer(const QString& channel) { if(!m_channelNickChangedTimer->isActive()) m_channelNickChangedTimer->start(); m_changedChannels.append(channel); } void Server::sendChannelNickChangedSignals() { foreach(const QString& channel, m_changedChannels) { if (m_joinedChannels.contains (channel)) { emit channelNickChanged(channel); foreach(ChannelNickPtr nick, (*m_joinedChannels[channel])) { if(nick->isChanged()) { nick->setChanged(false); } } } } m_changedChannels.clear(); } void Server::involuntaryQuit() { if((m_connectionState == Konversation::SSConnected || m_connectionState == Konversation::SSConnecting) && (m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHost) && m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHostIPv6))) { quitServer(); updateConnectionState(Konversation::SSInvoluntarilyDisconnected); } } void Server::reconnectInvoluntary() { if(m_connectionState == Konversation::SSInvoluntarilyDisconnected) reconnectServer(); } void Server::initCapablityNames() { m_capabilityNames.insert("away-notify", AwayNotify); m_capabilityNames.insert("extended-join", ExtendedJoin); m_capabilityNames.insert("server-time", ServerTime); m_capabilityNames.insert("znc.in/server-time-iso", ServerTime); m_capabilityNames.insert("userhost-in-names", UserHostInNames); m_capabilityNames.insert("sasl", SASL); m_capabilityNames.insert("multi-prefix", MultiPrefix); m_capabilityNames.insert("account-notify", AccountNotify); m_capabilityNames.insert("znc.in/self-message", SelfMessage); } // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on; // vim: set et sw=4 ts=4 cino=l1,cs,U1: diff --git a/src/viewer/channeloptionsdialog.cpp b/src/viewer/channeloptionsdialog.cpp index be5c0c9c..f092d8da 100644 --- a/src/viewer/channeloptionsdialog.cpp +++ b/src/viewer/channeloptionsdialog.cpp @@ -1,612 +1,612 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2005-2007 Peter Simonsson Copyright (C) 2006 Dario Abatianni Copyright (C) 2006-2007 Eike Hein */ #include "channeloptionsdialog.h" #include "application.h" #include "channel.h" #include "topichistorymodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Konversation { ChannelOptionsDialog::ChannelOptionsDialog(Channel *channel) : QDialog(channel) { setWindowTitle( i18n("Channel Settings for %1", channel->getName() ) ); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &ChannelOptionsDialog::changeOptions); connect(buttonBox, &QDialogButtonBox::rejected, this, &ChannelOptionsDialog::reject); mainLayout->addWidget(buttonBox); buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); Q_ASSERT(channel); m_channel = channel; m_ui.setupUi(mainWidget); m_ui.addBan->setIcon(QIcon::fromTheme("list-add")); m_ui.updateBan->setIcon(QIcon::fromTheme("edit-rename")); m_ui.removeBan->setIcon(QIcon::fromTheme("list-remove")); QStandardItemModel *modesModel = new QStandardItemModel(m_ui.otherModesList); m_ui.otherModesList->setModel(modesModel); m_ui.otherModesList->hide(); m_ui.banListSearchLine->setTreeWidget(m_ui.banList); m_ui.topicHistoryView->setServer(m_channel->getServer()); m_ui.topicHistoryView->setModel(m_channel->getTopicHistory()); m_ui.topicHistorySearchLine->setProxy(qobject_cast(m_ui.topicHistoryView->model())); m_ui.topicHistorySearchLine->lineEdit()->setPlaceholderText(QString()); m_editingTopic = false; m_ui.topicEdit->setChannel(channel); m_ui.topicEdit->setMaximumLength(m_channel->getServer()->topicLength()); connect(m_ui.topicHistoryView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(topicHistoryItemClicked(QItemSelection))); connect(m_ui.toggleAdvancedModes, &QPushButton::clicked, this, &ChannelOptionsDialog::toggleAdvancedModes); connect(m_ui.topicEdit, &TopicEdit::undoAvailable, this, &ChannelOptionsDialog::topicBeingEdited); connect(this, &ChannelOptionsDialog::finished, m_ui.topicEdit, &TopicEdit::clear); connect(m_channel, &Channel::modesChanged, this, &ChannelOptionsDialog::refreshModes); connect(m_channel->getServer(), SIGNAL(channelNickChanged(QString)), this, SLOT(refreshEnableModes())); connect(m_channel, &Channel::banAdded, this, &ChannelOptionsDialog::addBan); connect(m_channel, &Channel::banRemoved, this, &ChannelOptionsDialog::removeBan); connect(m_channel, &Channel::banListCleared, m_ui.banList, &QTreeWidget::clear); connect(m_ui.addBan, &QPushButton::clicked, this, &ChannelOptionsDialog::addBanClicked); connect(m_ui.updateBan, &QPushButton::clicked, this, &ChannelOptionsDialog::updateBanClicked); connect(m_ui.removeBan, &QPushButton::clicked, this, &ChannelOptionsDialog::removeBanClicked); connect(m_ui.banList, &QTreeWidget::itemSelectionChanged, this, &ChannelOptionsDialog::banSelectionChanged); connect(m_ui.hostmask, &KLineEdit::textChanged, this, &ChannelOptionsDialog::hostmaskChanged); m_ui.topicModeChBox->setWhatsThis(whatsThisForMode('T')); m_ui.messageModeChBox->setWhatsThis(whatsThisForMode('N')); m_ui.secretModeChBox->setWhatsThis(whatsThisForMode('S')); m_ui.inviteModeChBox->setWhatsThis(whatsThisForMode('I')); m_ui.moderatedModeChBox->setWhatsThis(whatsThisForMode('M')); m_ui.keyModeChBox->setWhatsThis(whatsThisForMode('P')); m_ui.keyModeEdit->setWhatsThis(whatsThisForMode('P')); m_ui.userLimitEdit->setWhatsThis(whatsThisForMode('L')); m_ui.userLimitChBox->setWhatsThis(whatsThisForMode('L')); refreshBanList(); resize(QSize(450, 420)); } ChannelOptionsDialog::~ChannelOptionsDialog() { } void ChannelOptionsDialog::showEvent(QShowEvent* event) { if (!event->spontaneous()) { refreshAllowedChannelModes(); refreshModes(); m_ui.topicEdit->clear(); m_editingTopic = false; m_ui.topicHistoryView->selectionModel()->clearSelection(); const QModelIndex& currentTopic = m_ui.topicHistoryView->model()->index(m_ui.topicHistoryView->model()->rowCount() - 1, 0); m_ui.topicHistoryView->selectionModel()->select(currentTopic, QItemSelectionModel::Select); m_ui.topicHistoryView->scrollTo(currentTopic, QAbstractItemView::EnsureVisible); if (!m_ui.topicEdit->isReadOnly()) m_ui.topicEdit->setFocus(); KConfigGroup config(KSharedConfig::openConfig(), "ChannelOptionsDialog"); resize(config.readEntry("Size", sizeHint())); const QList& sizes = config.readEntry("SplitterSizes", QList()); if (!sizes.isEmpty()) m_ui.splitter->setSizes(sizes); Preferences::restoreColumnState(m_ui.banList, "BanList ViewSettings"); } QDialog::showEvent(event); } void ChannelOptionsDialog::hideEvent(QHideEvent* event) { KConfigGroup config(KSharedConfig::openConfig(), "ChannelOptionsDialog"); config.writeEntry("Size", size()); config.writeEntry("SplitterSizes", m_ui.splitter->sizes()); Preferences::saveColumnState(m_ui.banList, "BanList ViewSettings"); QDialog::hideEvent(event); } void ChannelOptionsDialog::changeOptions() { const QString& newTopic = topic(); const QString& oldTopic = m_channel->getTopic(); if (newTopic != oldTopic) { // Pass a ^A so we can determine if we want to clear the channel topic. if (newTopic.isEmpty()) { if (!oldTopic.isEmpty()) m_channel->sendText(Preferences::self()->commandChar() + "TOPIC " + m_channel->getName() + " \x01"); } else m_channel->sendText(Preferences::self()->commandChar() + "TOPIC " + m_channel->getName() + ' ' + newTopic); } QStringList newModeList = modes(); QStringList currentModeList = m_channel->getModeList(); QStringList rmModes; QStringList addModes; QStringList tmp; QString modeString; bool plus; QString command("MODE %1 %2%3 %4"); for(QStringList::ConstIterator it = newModeList.constBegin(); it != newModeList.constEnd(); ++it) { modeString = (*it).mid(1); plus = ((*it)[0] == '+'); tmp = currentModeList.filter(QRegExp('^' + modeString)); if(tmp.isEmpty() && plus) { m_channel->getServer()->queue(command.arg(m_channel->getName()).arg("+").arg(modeString[0]).arg(modeString.mid(1))); } else if(!tmp.isEmpty() && !plus) { //FIXME: Bahamuth requires the key parameter for -k, but ircd breaks on -l with limit number. //Hence two versions of this. if (modeString[0] == 'k') m_channel->getServer()->queue(command.arg(m_channel->getName()).arg("-").arg(modeString[0]).arg(modeString.mid(1))); else m_channel->getServer()->queue(command.arg(m_channel->getName()).arg("-").arg(modeString[0]).arg(QString())); } } hide(); } void ChannelOptionsDialog::toggleAdvancedModes() { bool ison = m_ui.toggleAdvancedModes->isChecked(); m_ui.otherModesList->setVisible(ison); if(ison) { m_ui.toggleAdvancedModes->setText(i18n("&Hide Advanced Modes <<")); } else { m_ui.toggleAdvancedModes->setText(i18n("&Show Advanced Modes >>")); } } void ChannelOptionsDialog::topicBeingEdited(bool edited) { m_editingTopic = edited; m_ui.topicHistoryView->setTextSelectable(edited); } QString ChannelOptionsDialog::topic() { return m_ui.topicEdit->toPlainText().replace('\n', ' '); } void ChannelOptionsDialog::topicHistoryItemClicked(const QItemSelection& selection) { if (!m_editingTopic) { m_ui.topicEdit->clear(); if (!selection.isEmpty()) { m_ui.topicEdit->setUndoRedoEnabled(false); m_ui.topicEdit->setPlainText(m_ui.topicHistoryView->model()->data(selection.indexes().first()).toString()); m_ui.topicEdit->setUndoRedoEnabled(true); } } } void ChannelOptionsDialog::refreshEnableModes(bool forceUpdate) { if(!m_channel->getOwnChannelNick() || m_channel->getOwnChannelNick()->isChanged() || forceUpdate) { // cache the value m_isAnyTypeOfOp = m_channel->getOwnChannelNick() ? m_channel->getOwnChannelNick()->isAnyTypeOfOp() : false; m_ui.topicEdit->setReadOnly(!m_isAnyTypeOfOp && m_ui.topicModeChBox->isChecked()); m_ui.topicModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.messageModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.userLimitChBox->setEnabled(m_isAnyTypeOfOp); m_ui.userLimitEdit->setEnabled(m_isAnyTypeOfOp); m_ui.inviteModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.moderatedModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.secretModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.keyModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.keyModeEdit->setEnabled(m_isAnyTypeOfOp); QStandardItemModel* model = qobject_cast(m_ui.otherModesList->model()); if (model) { QList items = model->findItems("*", Qt::MatchWildcard, 0); items += model->findItems("*", Qt::MatchWildcard, 1); foreach (QStandardItem* item, items) item->setEnabled(m_isAnyTypeOfOp); } m_ui.addBan->setEnabled(m_isAnyTypeOfOp); m_ui.updateBan->setEnabled(m_isAnyTypeOfOp); m_ui.removeBan->setEnabled(m_isAnyTypeOfOp); banSelectionChanged(); m_ui.hostmask->setEnabled(m_isAnyTypeOfOp); hostmaskChanged(m_ui.hostmask->text()); } } void ChannelOptionsDialog::refreshAllowedChannelModes() { QString modeString = m_channel->getServer()->allowedChannelModes(); // These modes are handled in a special way: ntimslkbeI modeString.remove('t'); modeString.remove('n'); modeString.remove('l'); modeString.remove('i'); modeString.remove('m'); modeString.remove('s'); modeString.remove('k'); modeString.remove('b'); modeString.remove('e'); modeString.remove('I'); modeString.remove('O'); modeString.remove('o'); modeString.remove('v'); QStandardItemModel *modesModel = qobject_cast(m_ui.otherModesList->model()); modesModel->clear(); modesModel->setHorizontalHeaderLabels(QStringList() << i18n("Mode") << i18n("Parameter")); for(int i = 0; i < modeString.length(); i++) { QList newRow; QStandardItem *item = nullptr; if(!Preferences::self()->useLiteralModes() && getChannelModesHash().contains(modeString[i])) item = new QStandardItem(i18nc(" ()","%1 (%2)", modeString[i], getChannelModesHash().value(modeString[i]))); else item = new QStandardItem(QString(modeString[i])); item->setData(QString(modeString[i])); item->setCheckable(true); item->setEditable(false); newRow.append(item); item = new QStandardItem(); item->setEditable(true); newRow.append(item); modesModel->invisibleRootItem()->appendRow(newRow); } } void ChannelOptionsDialog::refreshModes() { QStringList modes = m_channel->getModeList(); m_ui.topicModeChBox->setChecked(false); m_ui.messageModeChBox->setChecked(false); m_ui.userLimitChBox->setChecked(false); m_ui.userLimitEdit->setValue(0); m_ui.inviteModeChBox->setChecked(false); m_ui.moderatedModeChBox->setChecked(false); m_ui.secretModeChBox->setChecked(false); m_ui.keyModeChBox->setChecked(false); m_ui.keyModeEdit->setText(QString()); QStandardItemModel *modesModel = qobject_cast(m_ui.otherModesList->model()); for (int i = 0; i < modesModel->rowCount(); ++i) { modesModel->item(i, 0)->setCheckState(Qt::Unchecked); } char mode; foreach (const QString ¤tMode, modes) { mode = currentMode[0].toLatin1(); switch(mode) { case 't': m_ui.topicModeChBox->setChecked(true); break; case 'n': m_ui.messageModeChBox->setChecked(true); break; case 'l': m_ui.userLimitChBox->setChecked(true); - m_ui.userLimitEdit->setValue(currentMode.mid(1).toInt()); + m_ui.userLimitEdit->setValue(currentMode.midRef(1).toInt()); break; case 'i': m_ui.inviteModeChBox->setChecked(true); break; case 'm': m_ui.moderatedModeChBox->setChecked(true); break; case 's': m_ui.secretModeChBox->setChecked(true); break; case 'k': m_ui.keyModeChBox->setChecked(true); m_ui.keyModeEdit->setText(currentMode.mid(1)); break; default: { bool found = false; QString modeString; modeString = mode; for (int i = 0; !found && i < modesModel->rowCount(); ++i) { QStandardItem *item = modesModel->item(i, 0); if (item->data().toString() == modeString) { found = true; item->setCheckState(Qt::Checked); modesModel->item(i, 1)->setText(currentMode.mid(1)); } } break; } } } refreshEnableModes(true); } QStringList ChannelOptionsDialog::modes() { QStringList modes; QString mode; mode = (m_ui.topicModeChBox->isChecked() ? "+" : "-"); mode += 't'; modes.append(mode); mode = (m_ui.messageModeChBox->isChecked() ? "+" : "-"); mode += 'n'; modes.append(mode); mode = (m_ui.userLimitChBox->isChecked() ? "+" : "-"); mode += 'l' + QString::number( m_ui.userLimitEdit->value() ); modes.append(mode); mode = (m_ui.inviteModeChBox->isChecked() ? "+" : "-"); mode += 'i'; modes.append(mode); mode = (m_ui.moderatedModeChBox->isChecked() ? "+" : "-"); mode += 'm'; modes.append(mode); mode = (m_ui.secretModeChBox->isChecked() ? "+" : "-"); mode += 's'; modes.append(mode); if (m_ui.keyModeChBox->isChecked() && !m_ui.keyModeEdit->text().isEmpty()) { mode = '+'; mode += 'k' + m_ui.keyModeEdit->text(); modes.append(mode); } else if (!m_ui.keyModeChBox->isChecked()) { mode = '-'; mode += 'k' + m_ui.keyModeEdit->text(); modes.append(mode); } QStandardItemModel *modesModel = qobject_cast(m_ui.otherModesList->model()); for (int i = 0; i < modesModel->rowCount(); ++i) { mode = (modesModel->item(i, 0)->checkState() == Qt::Checked ? "+" : "-"); mode += modesModel->item(i, 0)->data().toString() + modesModel->item(i, 1)->text(); modes.append(mode); } return modes; } // Ban List tab related functions void ChannelOptionsDialog::refreshBanList() { QStringList banlist = m_channel->getBanList(); m_ui.banList->clear(); for (QStringList::const_iterator it = --banlist.constEnd(); it != --banlist.constBegin(); --it) addBan((*it)); } void ChannelOptionsDialog::addBan(const QString& newban) { BanListViewItem *item = new BanListViewItem(m_ui.banList, newban.section(' ', 0, 0), newban.section(' ', 1, 1).section('!', 0, 0), newban.section(' ', 2 ,2).toUInt()); // set item as current item m_ui.banList->setCurrentItem(item); // update button states hostmaskChanged(m_ui.hostmask->text()); } void ChannelOptionsDialog::removeBan(const QString& ban) { QList items = m_ui.banList->findItems(ban, Qt::MatchCaseSensitive | Qt::MatchExactly, 0); if (items.count() > 0) delete items.at(0); } void ChannelOptionsDialog::addBanClicked() { QString newHostmask = m_ui.hostmask->text(); if (!newHostmask.isEmpty()) m_channel->getServer()->requestBan(QStringList(newHostmask), m_channel->getName(), QString()); } void ChannelOptionsDialog::removeBanClicked() { QString oldHostmask = m_ui.banList->currentItem()->text(0); // We delete the existing item because it's possible the server may // Modify the ban causing us not to catch it. If that happens we'll be // stuck with a stale item and a new item with the modified hostmask. delete m_ui.banList->currentItem(); // request unban m_channel->getServer()->requestUnban(oldHostmask, m_channel->getName()); } void ChannelOptionsDialog::updateBanClicked() { QString oldHostmask = m_ui.banList->currentItem()->text(0); QString newHostmask = m_ui.hostmask->text(); if (!newHostmask.isEmpty() && newHostmask.compare(oldHostmask)) { // We delete the existing item because it's possible the server may // Modify the ban causing us not to catch it. If that happens we'll be // stuck with a stale item and a new item with the modified hostmask. delete m_ui.banList->currentItem(); // request unban for the of the old hostmask m_channel->getServer()->requestUnban(oldHostmask, m_channel->getName()); // request ban for the of the old hostmask m_channel->getServer()->requestBan(QStringList(newHostmask), m_channel->getName(), QString()); } } /// Enables/disables updateBan and removeBan buttons depending on the currentItem of the banList void ChannelOptionsDialog::banSelectionChanged() { if (m_ui.banList->currentItem()) { m_ui.updateBan->setEnabled(m_isAnyTypeOfOp); m_ui.removeBan->setEnabled(m_isAnyTypeOfOp); // update line edit content m_ui.hostmask->setText(m_ui.banList->currentItem()->text(0)); } else { m_ui.updateBan->setEnabled(false); m_ui.removeBan->setEnabled(false); } } /// Enables/disables addBan and updateBan buttons depending on the value of @p text void ChannelOptionsDialog::hostmaskChanged(const QString& text) { if (text.trimmed().length() != 0) { if (m_isAnyTypeOfOp) { QList items = m_ui.banList->findItems(text, Qt::MatchExactly | Qt::MatchCaseSensitive, 0); m_ui.addBan->setEnabled(items.count() == 0); m_ui.updateBan->setEnabled(items.count() == 0 && m_ui.banList->currentItem()); } } else { m_ui.addBan->setEnabled(false); m_ui.updateBan->setEnabled(false); } } // This is our implementation of BanListViewItem BanListViewItem::BanListViewItem(QTreeWidget *parent) : QTreeWidgetItem() { parent->addTopLevelItem(this); } BanListViewItem::BanListViewItem (QTreeWidget *parent, const QString& label1, const QString& label2, uint timestamp) : QTreeWidgetItem() { setText(0, label1); setText(1, label2); m_timestamp.setTime_t(timestamp); setText(2, QLocale().toString(m_timestamp, QLocale::ShortFormat)); setData(2, Qt::UserRole, m_timestamp); parent->addTopLevelItem(this); } bool BanListViewItem::operator<(const QTreeWidgetItem &item) const { if (treeWidget()->sortColumn() == 2) { QVariant userdata = item.data(2, Qt::UserRole); if (userdata.isValid() && userdata.type() == QVariant::DateTime) { return m_timestamp < userdata.toDateTime(); } } return text(treeWidget()->sortColumn()) < item.text(treeWidget()->sortColumn()); } } QString Konversation::ChannelOptionsDialog::whatsThisForMode(char mode) { switch (mode) { case 'T': return i18n("

These control the mode of the channel. Only an operator can change these.

The Topic mode means that only the channel operator can change the topic for the channel.

"); case 'N': return i18n("

These control the mode of the channel. Only an operator can change these.

No messages from outside means users who are not in the channel cannot send messages for everybody in the channel to see. Almost all channels have this set to prevent nuisance messages.

"); case 'S': return i18n("

These control the mode of the channel. Only an operator can change these.

A Secret channel will not show up in the channel list, nor will any user be able to see that you are in the channel with the WHOIS command or anything similar. Only the people that are in the same channel will know that you are in this channel, if this mode is set.

"); case 'I': return i18n("

These control the mode of the channel. Only an operator can change these.

An Invite only channel means that people can only join the channel if they are invited. To invite someone, a channel operator needs to issue the command /invite nick from within the channel.

"); case 'P': return i18n("

These control the mode of the channel. Only an operator can change these.

A Private channel is shown in a listing of all channels, but the topic is not shown. A user's WHOIS may or may not show them as being in a private channel depending on the IRC server.

"); case 'M': return i18n("

These control the mode of the channel. Only an operator can change these.

A Moderated channel is one where only operators, half-operators and those with voice can talk.

"); case 'K': return i18n("

These control the mode of the channel. Only an operator can change these.

A protected channel requires users to enter a password in order to join.

"); case 'L': return i18n("

These control the mode of the channel. Only an operator can change these.

A channel that has a user Limit means that only that many users can be in the channel at any one time. Some channels have a bot that sits in the channel and changes this automatically depending on how busy the channel is.

"); default: qWarning() << "called for unknown mode" << mode; return QString(); } }