Make sure the ref count on the ChannelNicks drops to 0 on disconnect.
[konversation:konversation.git] / src / irc / server.cpp
1 // -*- mode: c++; c-file-style: "bsd"; c-basic-offset: 4; tabs-width: 4; indent-tabs-mode: nil -*-
2
3 /*
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; either version 2 of the License, or
7   (at your option) any later version.
8 */
9
10 /*
11   Copyright (C) 2002 Dario Abatianni <eisfuchs@tigress.com>
12   Copyright (C) 2005 Ismail Donmez <ismail@kde.org>
13   Copyright (C) 2005-2006 Peter Simonsson <psn@linux.se>
14   Copyright (C) 2006-2008 Eli J. MacKenzie <argonel at gmail.com>
15   Copyright (C) 2005-2008 Eike Hein <hein@kde.org>
16 */
17
18 #include "server.h"
19 #include "ircqueue.h"
20 #include "query.h"
21 #include "channel.h"
22 #include "application.h"
23 #include "connectionmanager.h"
24 #include "dcccommon.h"
25 #include "transfermanager.h"
26 #include "transfersend.h"
27 #include "transferrecv.h"
28 #include <chat.h>
29 #include "recipientdialog.h"
30 #include "nick.h"
31 #include "irccharsets.h"
32 #include "viewcontainer.h"
33 #include "statuspanel.h"
34 #include "rawlog.h"
35 #include "channellistpanel.h"
36 #include "scriptlauncher.h"
37 #include "servergroupsettings.h"
38 #include "addressbook.h"
39 #include "serverison.h"
40 #include "common.h"
41 #include "notificationhandler.h"
42 #include "abstractawaymanager.h"
43
44 #include <QRegExp>
45 #include <QTextCodec>
46 #include <QDateTime>
47 #include <QStringListModel>
48
49 #include <KLocale>
50 #include <KFileDialog>
51 #include <KInputDialog>
52 #include <KWindowSystem>
53
54 #include <solid/networking.h>
55
56 #include <kio/sslui.h>
57
58 using namespace Konversation;
59
60 int Server::m_availableConnectionId = 0;
61
62 Server::Server(QObject* parent, ConnectionSettings& settings) : QObject(parent)
63 {
64     m_connectionId = m_availableConnectionId;
65     m_availableConnectionId++;
66
67     setConnectionSettings(settings);
68
69     m_connectionState = Konversation::SSNeverConnected;
70
71     m_delayedConnectTimer = new QTimer(this);
72     m_delayedConnectTimer->setSingleShot(true);
73     connect(m_delayedConnectTimer, SIGNAL(timeout()), this, SLOT(connectToIRCServer()));
74
75     for (int i=0; i <= Application::instance()->countOfQueues(); i++)
76     {
77         //QList<int> r=Preferences::queueRate(i);
78         IRCQueue *q=new IRCQueue(this, Application::instance()->staticrates[i]); //FIXME these are supposed to be in the rc
79         m_queues.append(q);
80     }
81
82     m_processingIncoming = false;
83     m_identifyMsg = false;
84     m_autoIdentifyLock = false;
85     m_autoJoin = false;
86
87     m_nickIndices.clear();
88     m_nickIndices.append(0);
89
90     m_nickListModel = new QStringListModel(this);
91
92     m_currentLag = -1;
93     m_rawLog = 0;
94     m_channelListPanel = 0;
95     m_serverISON = 0;
96     m_away = false;
97     m_socket = 0;
98     m_prevISONList = QStringList();
99     m_bytesReceived = 0;
100     m_encodedBytesSent=0;
101     m_bytesSent=0;
102     m_linesSent=0;
103     // TODO fold these into a QMAP, and these need to be reset to RFC values if this server object is reused.
104     m_serverNickPrefixModes = "ovh";
105     m_serverNickPrefixes = "@+%";
106     m_banAddressListModes = 'b'; // {RFC-1459, draft-brocklesby-irc-isupport} -> pick one
107     m_channelPrefixes = "#&";
108     m_modesCount = 3;
109     m_showSSLConfirmation = true;
110
111     setObjectName(QString::fromLatin1("server_") + settings.name());
112
113     setNickname(settings.initialNick());
114     obtainNickInfo(getNickname());
115
116     m_statusView = getViewContainer()->addStatusView(this);
117
118     if (Preferences::self()->rawLog())
119         addRawLog(false);
120
121     m_inputFilter.setServer(this);
122     m_outputFilter = new Konversation::OutputFilter(this);
123     m_scriptLauncher = new ScriptLauncher(this);
124
125     // For /msg query completion
126     m_completeQueryPosition = 0;
127
128     updateAutoJoin(settings.oneShotChannelList());
129
130     if (!getIdentity()->getShellCommand().isEmpty())
131         QTimer::singleShot(0, this, SLOT(doPreShellCommand()));
132     else
133         QTimer::singleShot(0, this, SLOT(connectToIRCServer()));
134
135     initTimers();
136
137     if (getIdentity()->getShellCommand().isEmpty())
138         connectSignals();
139     // TODO FIXME this disappeared in a merge, ensure it should have
140     updateConnectionState(Konversation::SSNeverConnected);
141
142     connect(Konversation::Addressbook::self()->getAddressBook(), SIGNAL(addressBookChanged(AddressBook *)), this, SLOT(updateNickInfoAddressees()));
143     connect(Konversation::Addressbook::self(), SIGNAL(addresseesChanged()), this, SLOT(updateNickInfoAddressees()));
144
145     m_nickInfoChangedTimer = new QTimer(this);
146     m_nickInfoChangedTimer->setSingleShot(true);
147     m_nickInfoChangedTimer->setInterval(3000);
148     connect(m_nickInfoChangedTimer, SIGNAL(timeout()), this, SLOT(sendNickInfoChangedSignals()));
149
150     m_channelNickChangedTimer = new QTimer(this);
151     m_channelNickChangedTimer->setSingleShot(true);
152     m_channelNickChangedTimer->setInterval(1000);
153     connect(m_channelNickChangedTimer, SIGNAL(timeout()), this, SLOT(sendChannelNickChangedSignals()));
154 }
155
156 Server::~Server()
157 {
158     //send queued messages
159     kDebug() << "Server::~Server(" << getServerName() << ")";
160
161     // Delete helper object.
162     delete m_serverISON;
163     m_serverISON = 0;
164
165     // clear nicks online
166     emit nicksNowOnline(this,QStringList(),true);
167
168     // Make sure no signals get sent to a soon to be dying Server Window
169     if (m_socket)
170     {
171         m_socket->blockSignals(true);
172         m_socket->deleteLater();
173     }
174
175     if (m_statusView) delete m_statusView;
176
177     closeRawLog();
178     closeChannelListPanel();
179
180     qDeleteAll(m_channelList);
181     m_channelList.clear();
182
183     qDeleteAll(m_queryList);
184     m_queryList.clear();
185
186     purgeData();
187
188     //Delete the queues
189     qDeleteAll(m_queues);
190
191     emit destroyed(m_connectionId);
192
193     kDebug() << "~Server done";
194 }
195
196 void Server::purgeData()
197 {
198     // Delete all the NickInfos and ChannelNick structures.
199     m_allNicks.clear();
200
201     ChannelMembershipMap::ConstIterator it;
202
203     for ( it = m_joinedChannels.constBegin(); it != m_joinedChannels.constEnd(); ++it )
204         delete it.value();
205     m_joinedChannels.clear();
206
207     for ( it = m_unjoinedChannels.constBegin(); it != m_unjoinedChannels.constEnd(); ++it )
208         delete it.value();
209     m_unjoinedChannels.clear();
210
211     m_queryNicks.clear();
212 }
213
214 //... so called to match the ChatWindow derivatives.
215 bool Server::closeYourself(bool)
216 {
217     QTimer::singleShot(0, m_statusView, SLOT(serverSaysClose()));
218     return true;
219 }
220
221 void Server::doPreShellCommand()
222 {
223     QString command = getIdentity()->getShellCommand();
224     getStatusView()->appendServerMessage(i18n("Info"),"Running preconfigured command...");
225
226     connect(&m_preShellCommand,SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(preShellCommandExited(int, QProcess::ExitStatus)));
227     connect(&m_preShellCommand,SIGNAL(error(QProcess::ProcessError)), this, SLOT(preShellCommandError(QProcess::ProcessError)));
228
229     const QStringList commandList = command.split(' ');
230
231     for (QStringList::ConstIterator it = commandList.begin(); it != commandList.end(); ++it)
232         m_preShellCommand << *it;
233
234     m_preShellCommand.start();
235     if (m_preShellCommand.state() == QProcess::NotRunning) preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus());
236 }
237
238 void Server::initTimers()
239 {
240     m_notifyTimer.setObjectName("notify_timer");
241     m_notifyTimer.setSingleShot(true);
242     m_incomingTimer.setObjectName("incoming_timer");
243 }
244
245 void Server::connectSignals()
246 {
247     // Timers
248     connect(&m_incomingTimer, SIGNAL(timeout()), this, SLOT(processIncomingData()));
249     connect(&m_notifyTimer, SIGNAL(timeout()), this, SLOT(notifyTimeout()));
250     connect(&m_pingResponseTimer, SIGNAL(timeout()), this, SLOT(updateLongPongLag()));
251
252     // OutputFilter
253     connect(getOutputFilter(), SIGNAL(requestDccSend()), this,SLOT(requestDccSend()), Qt::QueuedConnection);
254     connect(getOutputFilter(), SIGNAL(requestDccSend(const QString&)), this, SLOT(requestDccSend(const QString&)), Qt::QueuedConnection);
255     connect(getOutputFilter(), SIGNAL(multiServerCommand(const QString&, const QString&)),
256         this, SLOT(sendMultiServerCommand(const QString&, const QString&)));
257     connect(getOutputFilter(), SIGNAL(reconnectServer(const QString&)), this, SLOT(reconnectServer(const QString&)));
258     connect(getOutputFilter(), SIGNAL(disconnectServer(const QString&)), this, SLOT(disconnectServer(const QString&)));
259     connect(getOutputFilter(), SIGNAL(quitServer(const QString&)), this, SLOT(quitServer(const QString&)));
260     connect(getOutputFilter(), SIGNAL(openDccSend(const QString &, KUrl)), this, SLOT(addDccSend(const QString &, KUrl)), Qt::QueuedConnection);
261     connect(getOutputFilter(), SIGNAL(openDccChat(const QString &)), this, SLOT(openDccChat(const QString &)), Qt::QueuedConnection);
262     connect(getOutputFilter(), SIGNAL(openDccWBoard(const QString &)), this, SLOT(openDccWBoard(const QString &)), Qt::QueuedConnection);
263     connect(getOutputFilter(), SIGNAL(acceptDccGet(const QString&, const QString&)),
264         this, SLOT(acceptDccGet(const QString&, const QString&)));
265     connect(getOutputFilter(), SIGNAL(sendToAllChannels(const QString&)), this, SLOT(sendToAllChannels(const QString&)));
266     connect(getOutputFilter(), SIGNAL(banUsers(const QStringList&,const QString&,const QString&)),
267         this, SLOT(requestBan(const QStringList&,const QString&,const QString&)));
268     connect(getOutputFilter(), SIGNAL(unbanUsers(const QString&,const QString&)),
269         this, SLOT(requestUnban(const QString&,const QString&)));
270     connect(getOutputFilter(), SIGNAL(openRawLog(bool)), this, SLOT(addRawLog(bool)));
271     connect(getOutputFilter(), SIGNAL(closeRawLog()), this, SLOT(closeRawLog()));
272     connect(getOutputFilter(), SIGNAL(encodingChanged()), this, SLOT(updateEncoding()));
273
274     Application* konvApp = static_cast<Application*>(kapp);
275     connect(getOutputFilter(), SIGNAL(connectTo(Konversation::ConnectionFlag, const QString&,
276                 const QString&, const QString&, const QString&, const QString&, bool)),
277             konvApp->getConnectionManager(), SLOT(connectTo(Konversation::ConnectionFlag,
278                 const QString&, const QString&, const QString&, const QString&, const QString&, bool)));
279     connect(konvApp->getDccTransferManager(), SIGNAL(newDccTransferQueued(Konversation::DCC::Transfer*)),
280             this, SLOT(slotNewDccTransferItemQueued(Konversation::DCC::Transfer*)));
281
282     connect(konvApp, SIGNAL(appearanceChanged()), this, SLOT(startNotifyTimer()));
283
284    // ViewContainer
285     connect(this, SIGNAL(showView(ChatWindow*)), getViewContainer(), SLOT(showView(ChatWindow*)));
286     connect(this, SIGNAL(addDccPanel()), getViewContainer(), SLOT(addDccPanel()));
287     connect(this, SIGNAL(addDccChat(Konversation::DCC::Chat*)),
288             getViewContainer(), SLOT(addDccChat(Konversation::DCC::Chat*)), Qt::QueuedConnection);
289     connect(this, SIGNAL(serverLag(Server*, int)), getViewContainer(), SIGNAL(updateStatusBarLagLabel(Server*, int)));
290     connect(this, SIGNAL(tooLongLag(Server*, int)), getViewContainer(), SIGNAL(setStatusBarLagLabelTooLongLag(Server*, int)));
291     connect(this, SIGNAL(resetLag()), getViewContainer(), SIGNAL(resetStatusBarLagLabel()));
292     connect(getOutputFilter(), SIGNAL(showView(ChatWindow*)), getViewContainer(), SLOT(showView(ChatWindow*)));
293     connect(getOutputFilter(), SIGNAL(openKonsolePanel()), getViewContainer(), SLOT(addKonsolePanel()));
294     connect(getOutputFilter(), SIGNAL(openChannelList(const QString&, bool)), getViewContainer(), SLOT(openChannelList(const QString&, bool)));
295     connect(getOutputFilter(), SIGNAL(closeDccPanel()), getViewContainer(), SLOT(closeDccPanel()));
296     connect(getOutputFilter(), SIGNAL(addDccPanel()), getViewContainer(), SLOT(addDccPanel()));
297
298     // Inputfilter - queued connections should be used for slots that have blocking UI
299     connect(&m_inputFilter, SIGNAL(addDccChat(const QString&,const QStringList&)),
300             this, SLOT(addDccChat(const QString&,const QStringList&)), Qt::QueuedConnection);
301     connect(&m_inputFilter, SIGNAL(rejectDccChat(const QString&)),
302             this, SLOT(rejectDccChat(const QString&)));
303     connect(&m_inputFilter, SIGNAL(startReverseDccChat(const QString&,const QStringList&)),
304             this, SLOT(startReverseDccChat(const QString&,const QStringList&)));
305     connect(&m_inputFilter, SIGNAL(welcome(const QString&)), this, SLOT(connectionEstablished(const QString&)));
306     connect(&m_inputFilter, SIGNAL(notifyResponse(const QString&)), this, SLOT(notifyResponse(const QString&)));
307     connect(&m_inputFilter, SIGNAL(startReverseDccSendTransfer(const QString&,const QStringList&)),
308         this, SLOT(startReverseDccSendTransfer(const QString&,const QStringList&)));
309     connect(&m_inputFilter, SIGNAL(addDccGet(const QString&, const QStringList&)),
310             this, SLOT(addDccGet(const QString&, const QStringList&)), Qt::QueuedConnection);
311     connect(&m_inputFilter, SIGNAL(resumeDccGetTransfer(const QString&, const QStringList&)),
312         this, SLOT(resumeDccGetTransfer(const QString&, const QStringList&)));
313     connect(&m_inputFilter, SIGNAL(resumeDccSendTransfer(const QString&, const QStringList&)),
314         this, SLOT(resumeDccSendTransfer(const QString&, const QStringList&)));
315     connect(&m_inputFilter, SIGNAL(rejectDccSendTransfer(const QString&, const QStringList&)),
316         this, SLOT(rejectDccSendTransfer(const QString&, const QStringList&)));
317     connect(&m_inputFilter, SIGNAL(userhost(const QString&,const QString&,bool,bool)),
318         this, SLOT(userhost(const QString&,const QString&,bool,bool)) );
319     connect(&m_inputFilter, SIGNAL(topicAuthor(const QString&,const QString&,QDateTime)),
320         this, SLOT(setTopicAuthor(const QString&,const QString&,QDateTime)) );
321     connect(&m_inputFilter, SIGNAL(endOfWho(const QString&)),
322         this, SLOT(endOfWho(const QString&)) );
323     connect(&m_inputFilter, SIGNAL(invitation(const QString&,const QString&)),
324         this,SLOT(invitation(const QString&,const QString&)) );
325     connect(&m_inputFilter, SIGNAL(addToChannelList(const QString&, int, const QString& )),
326         this, SLOT(addToChannelList(const QString&, int, const QString& )));
327
328     // Status View
329     connect(this, SIGNAL(serverOnline(bool)), getStatusView(), SLOT(serverOnline(bool)));
330
331     // Scripts
332     connect(getOutputFilter(), SIGNAL(launchScript(const QString&, const QString&)),
333         m_scriptLauncher, SLOT(launchScript(const QString&, const QString&)));
334     connect(m_scriptLauncher, SIGNAL(scriptNotFound(const QString&)),
335         this, SLOT(scriptNotFound(const QString&)));
336     connect(m_scriptLauncher, SIGNAL(scriptExecutionError(const QString&)),
337         this, SLOT(scriptExecutionError(const QString&)));
338
339     // Stats
340     connect(this, SIGNAL(sentStat(int, int)), SLOT(collectStats(int, int)));
341 }
342
343 int Server::getPort()
344 {
345     return getConnectionSettings().server().port();
346 }
347
348 int Server::getLag()  const
349 {
350     return m_currentLag;
351 }
352
353 bool Server::getAutoJoin()  const
354 {
355     return m_autoJoin;
356 }
357
358 void Server::setAutoJoin(bool on)
359 {
360     m_autoJoin = on;
361 }
362
363 void Server::preShellCommandExited(int exitCode, QProcess::ExitStatus exitStatus)
364 {
365     Q_UNUSED(exitCode);
366     if (exitStatus == QProcess::NormalExit)
367         getStatusView()->appendServerMessage(i18n("Info"),"Process executed successfully!");
368     else
369         getStatusView()->appendServerMessage(i18n("Warning"),"There was a problem while executing the command!");
370
371     connectToIRCServer();
372     connectSignals();
373 }
374
375 void Server::preShellCommandError(QProcess::ProcessError error)
376 {
377     Q_UNUSED(error);
378
379     getStatusView()->appendServerMessage(i18n("Warning"),"There was a problem while executing the command!");
380
381     connectToIRCServer();
382     connectSignals();
383 }
384
385 void Server::connectToIRCServer()
386 {
387     if (!isConnected())
388     {
389 // Reenable check when it works reliably for all backends
390 //         if(Solid::Networking::status() != Solid::Networking::Connected)
391 //         {
392 //             updateConnectionState(Konversation::SSInvoluntarilyDisconnected);
393 //             return;
394 //         }
395
396         updateConnectionState(Konversation::SSConnecting);
397
398         m_autoIdentifyLock = false;
399         m_ownIpByUserhost.clear();
400
401         resetQueues();
402
403         // This is needed to support server groups with mixed SSL and nonSSL servers
404         delete m_socket;
405         m_socket = 0;
406         if (m_referenceNicklist != getIdentity()->getNicknameList())
407             m_nickListModel->setStringList(getIdentity()->getNicknameList());
408         resetNickSelection();
409
410         m_socket = new KTcpSocket();
411         m_socket->setObjectName("serverSocket");
412
413         connect(m_socket, SIGNAL(error(KTcpSocket::Error)), SLOT(broken(KTcpSocket::Error)) );
414         connect(m_socket, SIGNAL(readyRead()), SLOT(incoming()));
415         connect(m_socket, SIGNAL(disconnected()), SLOT(closed()));
416
417         connect(m_socket, SIGNAL(hostFound()), SLOT (hostFound()));
418
419         // connect() will do a async lookup too
420         if(!getConnectionSettings().server().SSLEnabled())
421         {
422             connect(m_socket, SIGNAL(connected()), SLOT (ircServerConnectionSuccess()));
423             m_socket->connectToHost(getConnectionSettings().server().host(), getConnectionSettings().server().port());
424         }
425         else
426         {
427             connect(m_socket, SIGNAL(encrypted()), SLOT (ircServerConnectionSuccess()));
428             connect(m_socket, SIGNAL(sslErrors(const QList<KSslError>&)), SLOT(sslError(const QList<KSslError>&)));
429
430             m_socket->connectToHostEncrypted(getConnectionSettings().server().host(), getConnectionSettings().server().port());
431         }
432
433         // set up the connection details
434         setPrefixes(m_serverNickPrefixModes, m_serverNickPrefixes);
435         getStatusView()->appendServerMessage(i18n("Info"),i18n("Looking for server %1 (port <numid>%2</numid>) ...",
436             getConnectionSettings().server().host(),
437             QString::number(getConnectionSettings().server().port())));
438         // reset InputFilter (auto request info, /WHO request info)
439         m_inputFilter.reset();
440     }
441     else
442         kDebug() << "connectToIRCServer() called while already connected: This should never happen.";
443 }
444
445 void Server::connectToIRCServerIn(uint delay)
446 {
447     m_delayedConnectTimer->setInterval(delay * 1000);
448     m_delayedConnectTimer->start();
449
450     updateConnectionState(Konversation::SSScheduledToConnect);
451 }
452
453 void Server::showSSLDialog()
454 {
455         //TODO
456         /*
457           SSLSocket* sslsocket = dynamic_cast<SSLSocket*>(m_socket);
458
459           if (sslsocket) sslsocket->showInfoDialog();
460         */
461 }
462
463 // set available channel types according to 005 RPL_ISUPPORT
464 void Server::setChannelTypes(const QString &pre)
465 {
466     m_channelPrefixes = pre;
467 }
468
469 QString Server::getChannelTypes() const
470 {
471     return m_channelPrefixes;
472 }
473
474 // set max number of channel modes with parameter according to 005 RPL_ISUPPORT
475 void Server::setModesCount(int count)
476 {
477     m_modesCount = count;
478 }
479
480 int Server::getModesCount()
481 {
482     return m_modesCount;
483 }
484
485 // set user mode prefixes according to non-standard 005-Reply (see inputfilter.cpp)
486 void Server::setPrefixes(const QString &modes, const QString& prefixes)
487 {
488     // NOTE: serverModes is QString::null, if server did not supply the
489     // modes which relates to the network's nick-prefixes
490     m_serverNickPrefixModes = modes;
491     m_serverNickPrefixes = prefixes;
492 }
493
494 void Server::setChanModes(QString modes)
495 {
496     QStringList abcd = modes.split(',');
497     m_banAddressListModes = abcd.value(0);
498 }
499
500 // return a nickname without possible mode character at the beginning
501 void Server::mangleNicknameWithModes(QString& nickname,bool& isAdmin,bool& isOwner,
502 bool& isOp,bool& isHalfop,bool& hasVoice)
503 {
504     isAdmin = false;
505     isOwner = false;
506     isOp = false;
507     isHalfop = false;
508     hasVoice = false;
509
510     int modeIndex;
511
512     if (nickname.isEmpty()) return;
513
514     while ((modeIndex = m_serverNickPrefixes.indexOf(nickname[0])) != -1)
515     {
516         if(nickname.isEmpty())
517             return;
518         nickname = nickname.mid(1);
519         // cut off the prefix
520         bool recognisedMode = false;
521         // determine, whether status is like op or like voice
522         while((modeIndex)<int(m_serverNickPrefixes.length()) && !recognisedMode)
523         {
524             switch(m_serverNickPrefixes[modeIndex].toLatin1())
525             {
526                 case '*':                         // admin (EUIRC)
527                 {
528                     isAdmin = true;
529                     recognisedMode = true;
530                     break;
531                 }
532                 case '&':                         // admin (unrealircd)
533                 {
534                     isAdmin = true;
535                     recognisedMode = true;
536                     break;
537                 }
538                 case '!':                         // channel owner (RFC2811)
539                 {
540                     isOwner = true;
541                     recognisedMode = true;
542                     break;
543                 }
544                 case '~':                         // channel owner (unrealircd)
545                 {
546                     isOwner = true;
547                     recognisedMode = true;
548                     break;
549                 }
550                 case '@':                         // channel operator (RFC1459)
551                 {
552                     isOp = true;
553                     recognisedMode = true;
554                     break;
555                 }
556                 case '%':                         // halfop
557                 {
558                     isHalfop = true;
559                     recognisedMode = true;
560                     break;
561                 }
562                 case '+':                         // voiced (RFC1459)
563                 {
564                     hasVoice = true;
565                     recognisedMode = true;
566                     break;
567                 }
568                 default:
569                 {
570                     ++modeIndex;
571                     break;
572                 }
573             }                                     //switch to recognise the mode.
574         }                                         // loop through the modes to find one recognised
575     }                                             // loop through the name
576 }
577
578 void Server::hostFound()
579 {
580     getStatusView()->appendServerMessage(i18n("Info"),i18n("Server found, connecting..."));
581 }
582
583 void Server::ircServerConnectionSuccess()
584 {
585     emit sslConnected(this);
586     getConnectionSettings().setReconnectCount(0);
587
588     Konversation::ServerSettings serverSettings = getConnectionSettings().server();
589
590     connect(this, SIGNAL(nicknameChanged(const QString&)), getStatusView(), SLOT(setNickname(const QString&)));
591     getStatusView()->appendServerMessage(i18n("Info"),i18n("Connected; logging in..."));
592
593     QString connectString = "USER " +
594         getIdentity()->getIdent() +
595         " 8 * :" +                                // 8 = +i; 4 = +w
596         getIdentity()->getRealName();
597
598     QStringList ql;
599     if (!serverSettings.password().isEmpty())
600         ql << "PASS " + serverSettings.password();
601
602     ql << "NICK "+getNickname();
603     ql << connectString;
604     queueList(ql, HighPriority);
605
606     setNickname(getNickname());
607 }
608
609 void Server::broken(KTcpSocket::Error error)
610 {
611     Q_UNUSED(error);
612     kDebug() << "Connection broken " << m_socket->errorString() << "!";
613
614     m_socket->blockSignals(true);
615
616     resetQueues();
617
618     m_notifyTimer.stop();
619     m_pingResponseTimer.stop();
620     m_inputFilter.setLagMeasuring(false);
621     m_currentLag = -1;
622
623     purgeData();
624
625     // HACK Only show one nick change dialog at connection time.
626     // This hack is a bit nasty as it assumes that the only KDialog
627     // child of the statusview will be the nick change dialog.
628     if (getStatusView())
629     {
630         KDialog* nickChangeDialog = getStatusView()->findChild<KDialog*>();
631
632         if (nickChangeDialog) nickChangeDialog->reject();
633     }
634
635     emit resetLag();
636     emit nicksNowOnline(this,QStringList(),true);
637
638     updateAutoJoin();
639
640     if (getConnectionState() != Konversation::SSDeliberatelyDisconnected)
641     {
642         static_cast<Application*>(kapp)->notificationHandler()->connectionFailure(getStatusView(), getServerName());
643
644         QString error = i18n("Connection to server %1 (port <numid>%2</numid>) lost: %3.",
645             getConnectionSettings().server().host(),
646             QString::number(getConnectionSettings().server().port()),
647             m_socket->errorString());
648
649         getStatusView()->appendServerMessage(i18n("Error"), error);
650
651         updateConnectionState(Konversation::SSInvoluntarilyDisconnected);
652     }
653 }
654
655 bool Server::askUserToIgnoreSslErrors()
656 {
657     bool retVal = false;
658
659     // We are called by sslError:
660     // sslError is a slot, if it's signal is emitted multiple times
661     // then we only want to show the dialog once.
662     if ( m_showSSLConfirmation )
663     {
664         // We don't want to show any further SSL confirmation dialogs.
665         m_showSSLConfirmation = false;
666
667         // Ask the user if he wants to ignore SSL errors.
668         // In case the user wants to make the rule he chose (for example: always allow) persistent
669         // this will not show a dialog (but it will return "sslErrorsIgnored = true").
670         retVal = KIO::SslUi::askIgnoreSslErrors( m_socket, KIO::SslUi::RecallAndStoreRules );
671
672         // As we're done now we can show further SSL dialogs.
673         m_showSSLConfirmation = true;
674     }
675
676     return retVal;
677 }
678
679 void Server::sslError( const QList<KSslError>& errors )
680 {
681     // Ask the user if he wants to ignore the errors.
682     if ( askUserToIgnoreSslErrors() )
683     {
684         // The user has chosen to ignore SSL errors.
685         m_socket->ignoreSslErrors();
686
687         // Show a warning in the chat window that the SSL certificate failed the authenticity check.
688         QString error = i18n("The SSL certificate for the the server %1 (port <numid>%2</numid>) failed the authenticity check.",
689                             getConnectionSettings().server().host(),
690                             QString::number(getConnectionSettings().server().port()));
691
692         getStatusView()->appendServerMessage(i18n("SSL Connection Warning"), error);
693     }
694     else
695     {
696         QString errorReason;
697
698         for (int i = 0; i < errors.size(); ++i)
699         {
700             errorReason += errors.at(i).errorString() + ' ';
701         }
702
703         QString error = i18n("Could not connect to %1 (port <numid>%2</numid>) using SSL encryption. Either the server does not support SSL (did you use the correct port?) or you rejected the certificate. %3",
704             getConnectionSettings().server().host(),
705             QString::number(getConnectionSettings().server().port()),
706             errorReason);
707
708         getStatusView()->appendServerMessage(i18n("SSL Connection Error"), error);
709
710         emit sslInitFailure();
711     }
712 }
713
714 // Will be called from InputFilter as soon as the Welcome message was received
715 void Server::connectionEstablished(const QString& ownHost)
716 {
717     // Some servers don't include the userhost in RPL_WELCOME, so we
718     // need to use RPL_USERHOST to get ahold of our IP later on
719     if (!ownHost.isEmpty())
720         QHostInfo::lookupHost(ownHost, this, SLOT(gotOwnResolvedHostByWelcome(const QHostInfo&)));
721
722     updateConnectionState(Konversation::SSConnected);
723
724     // Make a helper object to build ISON (notify) list and map offline nicks to addressbook.
725     // TODO: Give the object a kick to get it started?
726     m_serverISON = new ServerISON(this);
727     // get first notify very early
728     startNotifyTimer(1000);
729     // Register with services
730     registerWithServices();
731     // get own ip by userhost
732     requestUserhost(getNickname());
733
734     // Start the PINGPONG match
735     QTimer::singleShot(1000 /*1 sec*/, this, SLOT(sendPing()));
736
737     // Recreate away state if we were set away prior to a reconnect.
738     if (m_away)
739     {
740         // Correct server's beliefs about its away state.
741         m_away = false;
742         requestAway(m_awayReason);
743     }
744 }
745
746 void Server::registerWithServices()
747 {
748     if (getIdentity() && !getIdentity()->getBot().isEmpty()
749         && !getIdentity()->getPassword().isEmpty()
750         && !m_autoIdentifyLock)
751     {
752         queue("PRIVMSG "+getIdentity()->getBot()+" :identify "+getIdentity()->getPassword(), HighPriority);
753
754         m_autoIdentifyLock = true;
755     }
756 }
757
758 //FIXME operator[] inserts an empty T& so each destination might just as well have its own key storage
759 QByteArray Server::getKeyForRecipient(const QString& recipient) const
760 {
761     return m_keyHash[recipient.toLower()];
762 }
763
764 void Server::setKeyForRecipient(const QString& recipient, const QByteArray& key)
765 {
766     m_keyHash[recipient.toLower()] = key;
767 }
768
769 void Server::gotOwnResolvedHostByWelcome(const QHostInfo& res)
770 {
771     if (res.error() == QHostInfo::NoError && !res.addresses().isEmpty())
772         m_ownIpByWelcome = res.addresses().first().toString();
773     else
774         kDebug() << "Got error: " << res.errorString();
775 }
776
777 bool Server::isSocketConnected() const
778 {
779     if (!m_socket) return false;
780
781     return (m_socket->state() == KTcpSocket::ConnectedState);
782 }
783
784 void Server::updateConnectionState(Konversation::ConnectionState state)
785 {
786     if (state != m_connectionState)
787     {
788         m_connectionState = state;
789
790         if (m_connectionState == Konversation::SSConnected)
791             emit serverOnline(true);
792         else if (m_connectionState != Konversation::SSConnecting)
793             emit serverOnline(false);
794
795        emit connectionStateChanged(this, state);
796     }
797 }
798
799 void Server::reconnectServer(const QString& quitMessage)
800 {
801     if (isConnecting() || isSocketConnected()) quitServer(quitMessage);
802
803     // Use asynchronous invocation so that the broken() that the above
804     // quitServer might cause is delivered before connectToIRCServer
805     // sets SSConnecting and broken() announces a deliberate disconnect
806     // due to the failure allegedly occurring during SSConnecting.
807     QTimer::singleShot(0, this, SLOT(connectToIRCServer()));
808 }
809
810 void Server::disconnectServer(const QString& quitMessage)
811 {
812     getConnectionSettings().setReconnectCount(0);
813
814     if (isScheduledToConnect())
815     {
816         m_delayedConnectTimer->stop();
817         getStatusView()->appendServerMessage(i18n("Info"), i18n("Delayed connect aborted."));
818     }
819
820     if (isSocketConnected()) quitServer(quitMessage);
821 }
822
823 void Server::quitServer(const QString& quitMessage)
824 {
825     // Make clear this is deliberate even if the QUIT never actually goes through the queue
826     // (i.e. this is not redundant with _send_internal()'s updateConnectionState() call for
827     // a QUIT).
828     updateConnectionState(Konversation::SSDeliberatelyDisconnected);
829
830     if (!m_socket) return;
831
832     QString toServer = "QUIT :";
833
834     if (quitMessage.isEmpty())
835         toServer += getIdentity()->getQuitReason();
836     else
837         toServer += quitMessage;
838
839     queue(toServer, HighPriority);
840     
841     flushQueues();
842
843     // Close the socket to allow a dead connection to be reconnected before the socket timeout.
844     m_socket->close();
845
846     getStatusView()->appendServerMessage(i18n("Info"), i18n("Disconnected from %1 (port <numid>%2</numid>).",
847         getConnectionSettings().server().host(),
848         QString::number(getConnectionSettings().server().port())));
849 }
850
851 void Server::notifyAction(const QString& nick)
852 {
853     // parse wildcards (toParse,nickname,channelName,nickList,parameter)
854     QString out = parseWildcards(Preferences::self()->notifyDoubleClickAction(),
855         getNickname(),
856         QString(),
857         QString(),
858         nick,
859         QString());
860
861     // Send all strings, one after another
862     QStringList outList = out.split('\n', QString::SkipEmptyParts);
863     for (int index=0; index<outList.count(); ++index)
864     {
865         Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),outList[index],QString());
866         queue(result.toServer);
867     }                                             // endfor
868 }
869
870 void Server::notifyResponse(const QString& nicksOnline)
871 {
872     bool nicksOnlineChanged = false;
873     QStringList actualList = nicksOnline.split(' ', QString::SkipEmptyParts);
874     QString lcActual = ' ' + nicksOnline + ' ';
875     QString lcPrevISON = ' ' + (m_prevISONList.join(" ")) + ' ';
876
877     QStringList::iterator it;
878
879     //Are any nicks gone offline
880     for (it = m_prevISONList.begin(); it != m_prevISONList.end(); ++it)
881     {
882         if (!lcActual.contains(' ' + (*it) + ' ', Qt::CaseInsensitive))
883         {
884             setNickOffline(*it);
885             nicksOnlineChanged = true;
886         }
887     }
888
889     //Are any nicks gone online
890     for (it = actualList.begin(); it != actualList.end(); ++it)
891     {
892         if (!lcPrevISON.contains(' ' + (*it) + ' ', Qt::CaseInsensitive)) {
893             setWatchedNickOnline(*it);
894             nicksOnlineChanged = true;
895         }
896     }
897
898     // Note: The list emitted in this signal *does* include nicks in joined channels.
899     emit nicksNowOnline(this, actualList, nicksOnlineChanged);
900
901     m_prevISONList = actualList;
902
903     // Next round
904     startNotifyTimer();
905 }
906
907 void Server::startNotifyTimer(int msec)
908 {
909     // make sure the timer gets started properly in case we have reconnected
910     m_notifyTimer.stop();
911
912     if (msec == 0) msec = Preferences::self()->notifyDelay()*1000;
913
914     // start the timer in one shot mode
915     if (Preferences::self()->useNotify())
916         m_notifyTimer.start(msec);
917 }
918
919 void Server::notifyTimeout()
920 {
921     // Notify delay time is over, send ISON request if desired
922     if (Preferences::self()->useNotify())
923     {
924         // But only if there actually are nicks in the notify list
925         QString list = getISONListString();
926
927         if (!list.isEmpty()) queue("ISON "+list, LowPriority);
928
929     }
930 }
931
932 void Server::autoCommandsAndChannels()
933 {
934     if (getServerGroup() && !getServerGroup()->connectCommands().isEmpty())
935     {
936         QString connectCommands = getServerGroup()->connectCommands();
937
938         if (!getNickname().isEmpty())
939             connectCommands.replace("%nick", getNickname());
940
941         QStringList connectCommandsList = connectCommands.split(';', QString::SkipEmptyParts);
942         QStringList::iterator iter;
943
944         for (iter = connectCommandsList.begin(); iter != connectCommandsList.end(); ++iter)
945         {
946             QString output(*iter);
947             output = output.simplified();
948             getOutputFilter()->replaceAliases(output);
949             Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),output,QString());
950             queue(result.toServer);
951         }
952     }
953
954     if (getAutoJoin())
955     {
956         for ( QStringList::Iterator it = m_autoJoinCommands.begin(); it != m_autoJoinCommands.end(); ++it )
957             queue((*it));
958     }
959
960     if (!m_connectionSettings.oneShotChannelList().isEmpty())
961     {
962         QStringList oneShotJoin = generateJoinCommand(m_connectionSettings.oneShotChannelList());
963         for ( QStringList::Iterator it = oneShotJoin.begin(); it != oneShotJoin.end(); ++it )
964         {
965             queue((*it));
966         }
967         m_connectionSettings.clearOneShotChannelList();
968     }
969 }
970
971 /** Create a set of indices into the nickname list of the current identity based on the current nickname.
972  *
973  * The index list is only used if the current nickname is not available. If the nickname is in the identity,
974  * we do not want to retry it. If the nickname is not in the identity, it is considered to be at position -1.
975  */
976 void Server::resetNickSelection()
977 {
978     m_nickIndices.clear();
979     //for equivalence testing in case the identity gets changed underneath us
980     m_referenceNicklist = getIdentity()->getNicknameList();
981     //where in this identities nicklist will we have started?
982     int start = m_referenceNicklist.indexOf(getNickname());
983     int len = m_referenceNicklist.count();
984
985     //we first use this list of indices *after* we've already tried the current nick, which we don't want
986     //to retry if we wrapped, so exclude its index here
987     //if it wasn't in the list, we get -1 back, so then we *want* to include 0
988     for (int i=start+1; i<len; i++)
989         m_nickIndices.append(i);
990     //now, from the beginning of the list, to the item before start
991     for (int i=0; i<start; i++)
992         m_nickIndices.append(i);
993 }
994
995 QString Server::getNextNickname()
996 {
997      //if the identity changed underneath us (likely impossible), start over
998     if (m_referenceNicklist != getIdentity()->getNicknameList())
999         resetNickSelection();
1000
1001     QString newNick;
1002
1003     if (!m_nickIndices.isEmpty())
1004     {
1005         newNick = getIdentity()->getNickname(m_nickIndices.takeFirst());
1006     }
1007     else
1008     {
1009         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());
1010         newNick = KInputDialog::getText(i18n("Nickname error"), inputText,
1011                                         QString(), 0, getStatusView());
1012     }
1013     return newNick;
1014 }
1015
1016 void Server::processIncomingData()
1017 {
1018     m_incomingTimer.stop();
1019
1020     if (!m_inputBuffer.isEmpty() && !m_processingIncoming)
1021     {
1022         m_processingIncoming = true;
1023         QString front(m_inputBuffer.front());
1024         m_inputBuffer.pop_front();
1025         m_inputFilter.parseLine(front);
1026         m_processingIncoming = false;
1027
1028         if (!m_inputBuffer.isEmpty()) m_incomingTimer.start(0);
1029     }
1030 }
1031
1032 void Server::incoming()
1033 {
1034     //if (getConnectionSettings().server().SSLEnabled())
1035     //    emit sslConnected(this);
1036
1037
1038     //if (len <= 0 && getConnectionSettings().server().SSLEnabled())
1039     //    return;
1040
1041     // split buffer to lines
1042     QList<QByteArray> bufferLines;
1043     while (m_socket->canReadLine())
1044     {
1045         QByteArray line(m_socket->readLine());
1046         //remove \n blowfish doesn't like it
1047         int i = line.size()-1;
1048         while (i >= 0 && (line[i]=='\n' || line[i]=='\r')) // since euIRC gets away with sending just \r, bet someone sends \n\r?
1049         {
1050             i--;
1051         }
1052         line.truncate(i+1);
1053
1054         if (line.size() > 0)
1055             bufferLines.append(line);
1056     }
1057
1058     while(!bufferLines.isEmpty())
1059     {
1060         // Pre parsing is needed in case encryption/decryption is needed
1061         // BEGIN set channel encoding if specified
1062         QString senderNick;
1063         bool isServerMessage = false;
1064         QString channelKey;
1065         QTextCodec* codec = getIdentity()->getCodec();
1066         QByteArray first = bufferLines.first();
1067
1068         QStringList lineSplit = codec->toUnicode(first).split(' ', QString::SkipEmptyParts);
1069
1070         if( lineSplit.count() >= 1 )
1071         {
1072             if( lineSplit[0][0] == ':' )          // does this message have a prefix?
1073             {
1074                 if( !lineSplit[0].contains('!') ) // is this a server(global) message?
1075                     isServerMessage = true;
1076                 else
1077                     senderNick = lineSplit[0].mid(1, lineSplit[0].indexOf('!')-1);
1078
1079                 lineSplit.removeFirst();          // remove prefix
1080             }
1081         }
1082         else
1083         {
1084             // The line contained only spaces (other than CRLF, removed above)
1085             // and thus there's nothing more we can do with it.
1086             bufferLines.removeFirst();
1087             continue;
1088         }
1089
1090         // BEGIN pre-parse to know where the message belongs to
1091         QString command = lineSplit[0].toLower();
1092         if( isServerMessage )
1093         {
1094             if( lineSplit.count() >= 3 )
1095             {
1096                 if( command == "332" )            // RPL_TOPIC
1097                     channelKey = lineSplit[2];
1098                 if( command == "372" )            // RPL_MOTD
1099                     channelKey = ":server";
1100             }
1101         }
1102         else                                      // NOT a global message
1103         {
1104             if( lineSplit.count() >= 2 )
1105             {
1106                 // query
1107                 if( ( command == "privmsg" ||
1108                     command == "notice"  ) &&
1109                     lineSplit[1] == getNickname() )
1110                 {
1111                     channelKey = senderNick;
1112                 }
1113                 // channel message
1114                 else if( command == "privmsg" ||
1115                     command == "notice"  ||
1116                     command == "join"    ||
1117                     command == "kick"    ||
1118                     command == "part"    ||
1119                     command == "topic"   )
1120                 {
1121                     channelKey = lineSplit[1];
1122                 }
1123             }
1124         }
1125         // END pre-parse to know where the message belongs to
1126         // Decrypt if necessary
1127
1128         //send to raw log before decryption
1129         if(m_rawLog)
1130             m_rawLog->appendRaw("&gt;&gt; " + QString(first).remove(QChar(0xFDD0)).remove(QChar(0xFDD1)).replace('&',"&amp;").replace('<',"&lt;").replace('>',"&gt;").replace(QRegExp("\\s"), "&nbsp;"));
1131
1132         #ifdef HAVE_QCA2
1133         QByteArray cKey = getKeyForRecipient(channelKey);
1134         if(!cKey.isEmpty())
1135         {
1136             if(command == "privmsg")
1137             {
1138                 //only send encrypted text to decrypter
1139                 int index = first.indexOf(":",first.indexOf(":")+1);
1140                 if(this->identifyMsgEnabled()) // Workaround braindead Freenode prefixing messages with +
1141                     ++index;
1142                 QByteArray backup = first.mid(0,index+1);
1143
1144                 if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey))
1145                     first = getChannelByName(channelKey)->getCipher()->decrypt(first.mid(index+1));
1146                 else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey))
1147                     first = getQueryByName(channelKey)->getCipher()->decrypt(first.mid(index+1));
1148
1149                 first.prepend(backup);
1150             }
1151             else if(command == "332" || command == "topic")
1152             {
1153                 //only send encrypted text to decrypter
1154                 int index = first.indexOf(":",first.indexOf(":")+1);
1155                 QByteArray backup = first.mid(0,index+1);
1156
1157                 if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey))
1158                     first = getChannelByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1));
1159                 else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey))
1160                     first = getQueryByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1));
1161
1162                 first.prepend(backup);
1163             }
1164         }
1165         #endif
1166         bool isUtf8 = Konversation::isUtf8(first);
1167
1168         if (isUtf8)
1169             m_inputBuffer << QString::fromUtf8(first);
1170         else
1171         {
1172             // check setting
1173             QString channelEncoding;
1174             if( !channelKey.isEmpty() )
1175             {
1176                 if(getServerGroup())
1177                     channelEncoding = Preferences::channelEncoding(getServerGroup()->id(), channelKey);
1178                 else
1179                     channelEncoding = Preferences::channelEncoding(getDisplayName(), channelKey);
1180             }
1181             // END set channel encoding if specified
1182
1183             if( !channelEncoding.isEmpty() )
1184                 codec = Konversation::IRCCharsets::self()->codecForName(channelEncoding);
1185
1186             // if channel encoding is utf-8 and the string is definitely not utf-8
1187             // then try latin-1
1188             if (codec->mibEnum() == 106)
1189                 codec = QTextCodec::codecForMib( 4 /* iso-8859-1 */ );
1190
1191             m_inputBuffer << codec->toUnicode(first);
1192         }
1193
1194         bufferLines.removeFirst();
1195
1196         // Qt uses 0xFDD0 and 0xFDD1 to mark the beginning and end of text frames. Remove
1197         // these here to avoid fatal errors encountered in QText* and the event loop pro-
1198         // cessing.
1199         sterilizeUnicode(m_inputBuffer.back());
1200
1201         //FIXME: This has nothing to do with bytes, and it's not raw received bytes either. Bogus number.
1202         //m_bytesReceived+=m_inputBuffer.back().length();
1203     }
1204
1205     if( !m_incomingTimer.isActive() && !m_processingIncoming )
1206         m_incomingTimer.start(0);
1207 }
1208
1209 /** Calculate how long this message premable will be.
1210
1211     This is necessary because the irc server will clip messages so that the
1212     client receives a maximum of 512 bytes at once.
1213 */
1214 int Server::getPreLength(const QString& command, const QString& dest)
1215 {
1216     NickInfoPtr info = getNickInfo(getNickname());
1217     int hostMaskLength = 0;
1218
1219     if(info)
1220         hostMaskLength = info->getHostmask().length();
1221
1222     //:Sho_!i=ehs1@konversation/developer/hein PRIVMSG #konversation :and then back to it
1223
1224     //<colon>$nickname<!>$hostmask<space>$command<space>$destination<space><colon>$message<cr><lf>
1225     int x= 512 - 8 - (m_nickname.length() + hostMaskLength + command.length() + dest.length());
1226
1227     return x;
1228 }
1229
1230 //Commands greater than 1 have localizeable text:         0   1    2       3      4    5    6
1231 static QStringList outcmds = QString("WHO QUIT PRIVMSG NOTICE KICK PART TOPIC").split(QChar(' '));
1232
1233 int Server::_send_internal(QString outputLine)
1234 {
1235     QStringList outputLineSplit = outputLine.split(' ', QString::SkipEmptyParts);
1236
1237     int outboundCommand = -1;
1238     if (!outputLineSplit.isEmpty()) {
1239         //Lets cache the uppercase command so we don't miss or reiterate too much
1240         outboundCommand = outcmds.indexOf(outputLineSplit[0].toUpper());
1241     }
1242
1243     if (outputLine.at(outputLine.length()-1) == '\n')
1244     {
1245         kDebug() << "found \\n on " << outboundCommand;
1246         outputLine.resize(outputLine.length()-1);
1247     }
1248
1249     // remember the first arg of /WHO to identify responses
1250     if (outboundCommand == 0 && outputLineSplit.count() >= 2) //"WHO"
1251         m_inputFilter.addWhoRequest(outputLineSplit[1]);
1252     else if (outboundCommand == 1) //"QUIT"
1253         updateConnectionState(Konversation::SSDeliberatelyDisconnected);
1254
1255     // set channel encoding if specified
1256     QString channelCodecName;
1257
1258     //[ PRIVMSG | NOTICE | KICK | PART | TOPIC ] target :message
1259     if (outputLineSplit.count() > 2 && outboundCommand > 1)
1260     {
1261         if(getServerGroup()) // if we're connecting via a servergroup
1262             channelCodecName=Preferences::channelEncoding(getServerGroup()->id(), outputLineSplit[1]);
1263         else //if we're connecting to a server manually
1264             channelCodecName=Preferences::channelEncoding(getDisplayName(), outputLineSplit[1]);
1265     }
1266     QTextCodec* codec = 0;
1267     if (channelCodecName.isEmpty())
1268         codec = getIdentity()->getCodec();
1269     else
1270         codec = Konversation::IRCCharsets::self()->codecForName(channelCodecName);
1271
1272     // Some codecs don't work with a negative value. This is a bug in Qt 3.
1273     // ex.: JIS7, eucJP, SJIS
1274     //int outlen=-1;
1275
1276     //leaving this done twice for now, I'm uncertain of the implications of not encoding other commands
1277     QByteArray encoded = outputLine.toUtf8();
1278     if(codec)
1279         encoded = codec->fromUnicode(outputLine);
1280
1281     #ifdef HAVE_QCA2
1282     QString cipherKey;
1283     if (outputLineSplit.count() > 2 && outboundCommand > 1)
1284         cipherKey = getKeyForRecipient(outputLineSplit.at(1));
1285     if (!cipherKey.isEmpty())
1286     {
1287         int colon = outputLine.indexOf(':');
1288         if (colon > -1)
1289         {
1290             colon++;
1291
1292             QString pay(outputLine.mid(colon));
1293             //only encode the actual user text, IRCD *should* desire only ASCII 31 < x < 127 for protocol elements
1294             QByteArray payload = pay.toUtf8();
1295
1296             if(codec)
1297                 payload=codec->fromUnicode(pay);
1298             //apparently channel name isn't a protocol element...
1299             QByteArray dest = codec->fromUnicode(outputLineSplit.at(1));
1300
1301             if (outboundCommand == 2 || outboundCommand == 6) // outboundCommand == 3
1302             {
1303                 bool doit = true;
1304                 if (outboundCommand == 2)
1305                 {
1306                     //if its a privmsg and a ctcp but not an action, don't encrypt
1307                     //not interpreting `payload` in case encoding bollixed it
1308                     if (outputLineSplit.at(2).startsWith(QLatin1String(":\x01")) && outputLineSplit.at(2) != ":\x01""ACTION")
1309                         doit = false;
1310                 }
1311                 if (doit)
1312                 {
1313                     QString target = outputLineSplit.at(1);
1314
1315                     if(getChannelByName(target) && getChannelByName(target)->getCipher()->setKey(cipherKey.toLocal8Bit()))
1316                         getChannelByName(target)->getCipher()->encrypt(payload);
1317                     else if(getQueryByName(target) && getQueryByName(target)->getCipher()->setKey(cipherKey.toLocal8Bit()))
1318                         getQueryByName(target)->getCipher()->encrypt(payload);
1319
1320                     encoded = outputLineSplit.at(0).toAscii();
1321                     kDebug() << payload << "\n" << payload.data();
1322                     //two lines because the compiler insists on using the wrong operator+
1323                     encoded += ' ' + dest + " :" + payload;
1324                 }
1325             }
1326         }
1327     }
1328     #endif
1329     encoded += '\n';
1330     qint64 sout = m_socket->write(encoded, encoded.length());
1331
1332     if (m_rawLog)
1333         m_rawLog->appendRaw("&lt;&lt; " + encoded.replace('&',"&amp;").replace('<',"&lt;").replace('>',"&gt;"));
1334
1335     return sout;
1336 }
1337
1338 void Server::toServer(QString&s, IRCQueue* q)
1339 {
1340
1341     int sizesent = _send_internal(s);
1342     emit sentStat(s.length(), sizesent, q); //tell the queues what we sent
1343     //tell everyone else
1344     emit sentStat(s.length(), sizesent);
1345 }
1346
1347 void Server::collectStats(int bytes, int encodedBytes)
1348 {
1349     m_bytesSent += bytes;
1350     m_encodedBytesSent += encodedBytes;
1351     m_linesSent++;
1352 }
1353
1354 bool Server::validQueue(QueuePriority priority)
1355 {
1356    if (priority >=0 && priority <= Application::instance()->countOfQueues())
1357        return true;
1358    return false;
1359 }
1360
1361 bool Server::queue(const QString& line, QueuePriority priority)
1362 {
1363     if (!line.isEmpty() && validQueue(priority))
1364     {
1365         IRCQueue& out=*m_queues[priority];
1366         out.enqueue(line);
1367         return true;
1368     }
1369     return false;
1370 }
1371
1372 bool Server::queueList(const QStringList& buffer, QueuePriority priority)
1373 {
1374     if (buffer.isEmpty() || !validQueue(priority))
1375         return false;
1376
1377     IRCQueue& out=*(m_queues[priority]);
1378
1379     for(int i=0;i<buffer.count();i++)
1380     {
1381         QString line(buffer.at(i));
1382         if (!line.isEmpty())
1383             out.enqueue(line);
1384     }
1385     return true;
1386 }
1387
1388 void Server::resetQueues()
1389 {
1390     for (int i=0; i <= Application::instance()->countOfQueues(); i++)
1391         m_queues[i]->reset();
1392 }
1393
1394 //this could flood you off, but you're leaving anyway...
1395 void Server::flushQueues()
1396 {
1397     int cue;
1398     do
1399     {
1400         cue=-1;
1401         int wait=0;
1402         for (int i=1; i <= Application::instance()->countOfQueues(); i++) //slow queue can rot
1403         {
1404             IRCQueue *queue=m_queues[i];
1405             //higher queue indices have higher priorty, higher queue priority wins tie
1406             if (!queue->isEmpty() && queue->currentWait()>=wait)
1407             {
1408                 cue=i;
1409                 wait=queue->currentWait();
1410             }
1411         }
1412         if (cue>-1)
1413             m_queues[cue]->sendNow();
1414     } while (cue>-1);
1415 }
1416
1417 void Server::closed()
1418 {
1419     broken(m_socket->error());
1420 }
1421
1422 void Server::dbusRaw(const QString& command)
1423 {
1424     if(command.startsWith(Preferences::self()->commandChar()))
1425     {
1426         queue(command.section(Preferences::self()->commandChar(), 1));
1427     }
1428     else
1429         queue(command);
1430 }
1431
1432 void Server::dbusSay(const QString& target,const QString& command)
1433 {
1434     if(isAChannel(target))
1435     {
1436         Channel* channel=getChannelByName(target);
1437         if(channel) channel->sendChannelText(command);
1438     }
1439     else
1440     {
1441         Query* query = getQueryByName(target);
1442         if(query==0)
1443         {
1444             NickInfoPtr nickinfo = obtainNickInfo(target);
1445             query=addQuery(nickinfo, true);
1446         }
1447         if(query)
1448         {
1449             if(!command.isEmpty())
1450                 query->sendQueryText(command);
1451             else
1452             {
1453                 query->adjustFocus();
1454                 getViewContainer()->getWindow()->show();
1455                 KWindowSystem::demandAttention(getViewContainer()->getWindow()->winId());
1456                 KWindowSystem::activateWindow(getViewContainer()->getWindow()->winId());
1457             }
1458         }
1459     }
1460 }
1461
1462 void Server::dbusInfo(const QString& string)
1463 {
1464     appendMessageToFrontmost(i18n("D-Bus"),string);
1465 }
1466
1467 void Server::ctcpReply(const QString &receiver,const QString &text)
1468 {
1469     queue("NOTICE "+receiver+" :"+'\x01'+text+'\x01');
1470 }
1471
1472 // Given a nickname, returns NickInfo object.   0 if not found.
1473 NickInfoPtr Server::getNickInfo(const QString& nickname)
1474 {
1475     QString lcNickname(nickname.toLower());
1476     if (m_allNicks.contains(lcNickname))
1477     {
1478         NickInfoPtr nickinfo = m_allNicks[lcNickname];
1479         Q_ASSERT(nickinfo);
1480         return nickinfo;
1481     }
1482     else
1483         return NickInfoPtr(); //! TODO FIXME null null null
1484 }
1485
1486 // Given a nickname, returns an existing NickInfo object, or creates a new NickInfo object.
1487 // Returns pointer to the found or created NickInfo object.
1488 NickInfoPtr Server::obtainNickInfo(const QString& nickname)
1489 {
1490     NickInfoPtr nickInfo = getNickInfo(nickname);
1491     if (!nickInfo)
1492     {
1493         nickInfo = new NickInfo(nickname, this);
1494         m_allNicks.insert(QString(nickname.toLower()), nickInfo);
1495     }
1496     return nickInfo;
1497 }
1498
1499 const NickInfoMap* Server::getAllNicks() { return &m_allNicks; }
1500
1501 // Returns the list of members for a channel in the joinedChannels list.
1502 // 0 if channel is not in the joinedChannels list.
1503 // Using code must not alter the list.
1504 const ChannelNickMap *Server::getJoinedChannelMembers(const QString& channelName) const
1505 {
1506     QString lcChannelName = channelName.toLower();
1507     if (m_joinedChannels.contains(lcChannelName))
1508         return m_joinedChannels[lcChannelName];
1509     else
1510         return 0;
1511 }
1512
1513 // Returns the list of members for a channel in the unjoinedChannels list.
1514 // 0 if channel is not in the unjoinedChannels list.
1515 // Using code must not alter the list.
1516 const ChannelNickMap *Server::getUnjoinedChannelMembers(const QString& channelName) const
1517 {
1518     QString lcChannelName = channelName.toLower();
1519     if (m_unjoinedChannels.contains(lcChannelName))
1520         return m_unjoinedChannels[lcChannelName];
1521     else
1522         return 0;
1523 }
1524
1525 // Searches the Joined and Unjoined lists for the given channel and returns the member list.
1526 // 0 if channel is not in either list.
1527 // Using code must not alter the list.
1528 const ChannelNickMap *Server::getChannelMembers(const QString& channelName) const
1529 {
1530     const ChannelNickMap *members = getJoinedChannelMembers(channelName);
1531     if (members)
1532         return members;
1533     else
1534         return getUnjoinedChannelMembers(channelName);
1535 }
1536
1537 // Returns pointer to the ChannelNick (mode and pointer to NickInfo) for a given channel and nickname.
1538 // 0 if not found.
1539 ChannelNickPtr Server::getChannelNick(const QString& channelName, const QString& nickname)
1540 {
1541     QString lcNickname = nickname.toLower();
1542     const ChannelNickMap *channelNickMap = getChannelMembers(channelName);
1543     if (channelNickMap)
1544     {
1545         if (channelNickMap->contains(lcNickname))
1546             return (*channelNickMap)[lcNickname];
1547         else
1548             return ChannelNickPtr(); //! TODO FIXME null null null
1549     }
1550     else
1551     {
1552         return ChannelNickPtr(); //! TODO FIXME null null null
1553     }
1554 }
1555
1556 // Updates a nickname in a channel.  If not on the joined or unjoined lists, and nick
1557 // is in the watch list, adds the channel and nick to the unjoinedChannels list.
1558 // If mode != 99, sets the mode for the nick in the channel.
1559 // Returns the NickInfo object if nick is on any lists, otherwise 0.
1560 ChannelNickPtr Server::setChannelNick(const QString& channelName, const QString& nickname, unsigned int mode)
1561 {
1562     QString lcNickname = nickname.toLower();
1563     // If already on a list, update mode.
1564     ChannelNickPtr channelNick = getChannelNick(channelName, lcNickname);
1565     if (!channelNick)
1566     {
1567         // Get watch list from preferences.
1568         QString watchlist=getWatchListString();
1569         // Create a lower case nick list from the watch list.
1570         QStringList watchLowerList = watchlist.toLower().split(' ', QString::SkipEmptyParts);
1571         // If on the watch list, add channel and nick to unjoinedChannels list.
1572         if (watchLowerList.contains(lcNickname))
1573         {
1574             channelNick = addNickToUnjoinedChannelsList(channelName, nickname);
1575             channelNick->setMode(mode);
1576         }
1577         else return ChannelNickPtr(); //! TODO FIXME null null null
1578     }
1579
1580     if (mode != 99) channelNick->setMode(mode);
1581     return channelNick;
1582 }
1583
1584 // Returns a list of all the joined channels that a nick is in.
1585 QStringList Server::getNickJoinedChannels(const QString& nickname)
1586 {
1587     QString lcNickname = nickname.toLower();
1588     QStringList channellist;
1589     ChannelMembershipMap::ConstIterator channel;
1590     for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel )
1591     {
1592         if (channel.value()->contains(lcNickname)) channellist.append(channel.key());
1593     }
1594     return channellist;
1595 }
1596
1597 // Returns a list of all the channels (joined or unjoined) that a nick is in.
1598 QStringList Server::getNickChannels(const QString& nickname)
1599 {
1600     QString lcNickname = nickname.toLower();
1601     QStringList channellist;
1602     ChannelMembershipMap::ConstIterator channel;
1603     for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel )
1604     {
1605         if (channel.value()->contains(lcNickname)) channellist.append(channel.key());
1606     }
1607     for( channel = m_unjoinedChannels.constBegin(); channel != m_unjoinedChannels.constEnd(); ++channel )
1608     {
1609         if (channel.value()->contains(lcNickname)) channellist.append(channel.key());
1610     }
1611     return channellist;
1612 }
1613
1614 bool Server::isNickOnline(const QString &nickname)
1615 {
1616     NickInfoPtr nickInfo = getNickInfo(nickname);
1617     return (!nickInfo.isNull());
1618 }
1619
1620 QString Server::getOwnIpByNetworkInterface()
1621 {
1622     return m_socket->localAddress().toString();
1623 }
1624
1625 QString Server::getOwnIpByServerMessage()
1626 {
1627     if(!m_ownIpByWelcome.isEmpty())
1628         return m_ownIpByWelcome;
1629     else if(!m_ownIpByUserhost.isEmpty())
1630         return m_ownIpByUserhost;
1631     else
1632         return QString();
1633 }
1634
1635 Query* Server::addQuery(const NickInfoPtr & nickInfo, bool weinitiated)
1636 {
1637     QString nickname = nickInfo->getNickname();
1638     // Only create new query object if there isn't already one with the same name
1639     Query* query = getQueryByName(nickname);
1640
1641     if (!query)
1642     {
1643         QString lcNickname = nickname.toLower();
1644         query = getViewContainer()->addQuery(this, nickInfo, weinitiated);
1645
1646         query->indicateAway(m_away);
1647
1648         connect(query, SIGNAL(sendFile(const QString&)),this, SLOT(requestDccSend(const QString&)));
1649         connect(this, SIGNAL(serverOnline(bool)), query, SLOT(serverOnline(bool)));
1650
1651         // Append query to internal list
1652         m_queryList.append(query);
1653
1654         m_queryNicks.insert(lcNickname, nickInfo);
1655
1656         if (!weinitiated)
1657             static_cast<Application*>(kapp)->notificationHandler()->query(query, nickname);
1658     }
1659
1660     // try to get hostmask if there's none yet
1661     if (query->getNickInfo()->getHostmask().isEmpty()) requestUserhost(nickname);
1662
1663     Q_ASSERT(query);
1664
1665     return query;
1666 }
1667
1668 void Server::closeQuery(const QString &name)
1669 {
1670     Query* query = getQueryByName(name);
1671     removeQuery(query);
1672
1673     // Update NickInfo.  If no longer on any lists, delete it altogether, but
1674     // only if not on the watch list.  ISON replies will determine whether the NickInfo
1675     // is deleted altogether in that case.
1676     QString lcNickname = name.toLower();
1677     m_queryNicks.remove(lcNickname);
1678     if (!isWatchedNick(name)) deleteNickIfUnlisted(name);
1679 }
1680
1681 void Server::closeChannel(const QString& name)
1682 {
1683     kDebug() << "Server::closeChannel(" << name << ")";
1684     Channel* channelToClose = getChannelByName(name);
1685
1686     if(channelToClose)
1687     {
1688         Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),
1689             Preferences::self()->commandChar() + "PART", name);
1690         queue(result.toServer);
1691     }
1692 }
1693
1694 void Server::requestChannelList()
1695 {
1696     m_inputFilter.setAutomaticRequest("LIST", QString(), true);
1697     queue(QString("LIST"));
1698 }
1699
1700 void Server::requestWhois(const QString& nickname)
1701 {
1702     m_inputFilter.setAutomaticRequest("WHOIS", nickname, true);
1703     queue("WHOIS "+nickname, LowPriority);
1704 }
1705
1706 void Server::requestWho(const QString& channel)
1707 {
1708     m_inputFilter.setAutomaticRequest("WHO", channel, true);
1709     queue("WHO "+channel, LowPriority);
1710 }
1711
1712 void Server::requestUserhost(const QString& nicks)
1713 {
1714     const QStringList nicksList = nicks.split(' ', QString::SkipEmptyParts);
1715     for(QStringList::ConstIterator it=nicksList.constBegin() ; it!=nicksList.constEnd() ; ++it)
1716         m_inputFilter.setAutomaticRequest("USERHOST", *it, true);
1717     queue("USERHOST "+nicks, LowPriority);
1718 }
1719
1720 void Server::requestTopic(const QString& channel)
1721 {
1722     m_inputFilter.setAutomaticRequest("TOPIC", channel, true);
1723     queue("TOPIC "+channel, LowPriority);
1724 }
1725
1726 void Server::resolveUserhost(const QString& nickname)
1727 {
1728     m_inputFilter.setAutomaticRequest("WHOIS", nickname, true);
1729     m_inputFilter.setAutomaticRequest("DNS", nickname, true);
1730     queue("WHOIS "+nickname, LowPriority); //FIXME when is this really used?
1731 }
1732
1733 void Server::requestBan(const QStringList& users,const QString& channel,const QString& a_option)
1734 {
1735     QString hostmask;
1736     QString option=a_option.toLower();
1737
1738     Channel* targetChannel=getChannelByName(channel);
1739
1740     for(int index=0;index<users.count();index++)
1741     {
1742         // first, set the ban mask to the specified nick
1743         QString mask=users[index];
1744         // did we specify an option?
1745         if(!option.isEmpty())
1746         {
1747             // try to find specified nick on the channel
1748             Nick* targetNick=targetChannel->getNickByName(mask);
1749             // if we found the nick try to find their hostmask
1750             if(targetNick)
1751             {
1752                 QString hostmask=targetNick->getChannelNick()->getHostmask();
1753                 // if we found the hostmask, add it to the ban mask
1754                 if(!hostmask.isEmpty())
1755                 {
1756                     mask=targetNick->getChannelNick()->getNickname()+'!'+hostmask;
1757
1758                     // adapt ban mask to the option given
1759                     if(option=="host")
1760                         mask="*!*@*."+hostmask.section('.',1);
1761                     else if(option=="domain")
1762                         mask="*!*@"+hostmask.section('@',1);
1763                     else if(option=="userhost")
1764                         mask="*!"+hostmask.section('@',0,0)+"@*."+hostmask.section('.',1);
1765                     else if(option=="userdomain")
1766                         mask="*!"+hostmask.section('@',0,0)+'@'+hostmask.section('@',1);
1767                 }
1768             }
1769         }
1770
1771         Konversation::OutputFilterResult result = getOutputFilter()->execBan(mask,channel);
1772         queue(result.toServer);
1773     }
1774 }
1775
1776 void Server::requestUnban(const QString& mask,const QString& channel)
1777 {
1778     Konversation::OutputFilterResult result = getOutputFilter()->execUnban(mask,channel);
1779     queue(result.toServer);
1780 }
1781
1782 void Server::requestDccSend()
1783 {
1784     requestDccSend(QString());
1785 }
1786
1787 void Server::sendURIs(const KUrl::List& uris, const QString& nick)
1788 {
1789     foreach(const KUrl& uri, uris)
1790          addDccSend(nick, uri);
1791 }
1792
1793 void Server::requestDccSend(const QString &a_recipient)
1794 {
1795     QString recipient(a_recipient);
1796     // if we don't have a recipient yet, let the user select one
1797     if (recipient.isEmpty())
1798     {
1799         recipient = recipientNick();
1800     }
1801
1802     // do we have a recipient *now*?
1803     if(!recipient.isEmpty())
1804     {
1805         KUrl::List fileURLs=KFileDialog::getOpenUrls(
1806             KUrl(),
1807             QString(),
1808             getViewContainer()->getWindow(),
1809             i18n("Select File(s) to Send to %1", recipient)
1810         );
1811         KUrl::List::const_iterator it;
1812         for ( it = fileURLs.constBegin() ; it != fileURLs.constEnd() ; ++it )
1813         {
1814             addDccSend( recipient, *it );
1815         }
1816     }
1817 }
1818
1819 void Server::slotNewDccTransferItemQueued(DCC::Transfer* transfer)
1820 {
1821     if (transfer->getConnectionId() == connectionId() )
1822     {
1823         kDebug() << "connecting slots for " << transfer->getFileName() << " [" << transfer->getType() << "]";
1824         if ( transfer->getType() == DCC::Transfer::Receive )
1825         {
1826             connect( transfer, SIGNAL( done( Konversation::DCC::Transfer* ) ), this, SLOT( dccGetDone( Konversation::DCC::Transfer* ) ) );
1827             connect( transfer, SIGNAL( statusChanged( Konversation::DCC::Transfer*, int, int ) ), this, SLOT( dccStatusChanged( Konversation::DCC::Transfer*, int, int ) ) );
1828         }
1829         else
1830         {
1831             connect( transfer, SIGNAL( done( Konversation::DCC::Transfer* ) ), this, SLOT( dccSendDone( Konversation::DCC::Transfer* ) ) );
1832             connect( transfer, SIGNAL( statusChanged( Konversation::DCC::Transfer*, int, int ) ), this, SLOT( dccStatusChanged( Konversation::DCC::Transfer*, int, int ) ) );
1833         }
1834     }
1835 }
1836
1837 void Server::addDccSend(const QString &recipient,KUrl fileURL, const QString &altFileName, quint64 fileSize)
1838 {
1839     if (!fileURL.isValid())
1840     {
1841         return;
1842     }
1843
1844     // We already checked that the file exists in output filter / requestDccSend() resp.
1845     DCC::TransferSend* newDcc = Application::instance()->getDccTransferManager()->newUpload();
1846
1847     newDcc->setConnectionId(connectionId());
1848
1849     newDcc->setPartnerNick(recipient);
1850     newDcc->setFileURL(fileURL);
1851     if (!altFileName.isEmpty())
1852         newDcc->setFileName(altFileName);
1853     if (fileSize != 0)
1854         newDcc->setFileSize(fileSize);
1855
1856     emit addDccPanel();
1857
1858     if (newDcc->queue())
1859         newDcc->start();
1860 }
1861
1862 QString Server::recoverDccFileName(const QStringList & dccArguments, int offset) const
1863 {
1864     QString fileName;
1865     if(dccArguments.count() > offset + 1)
1866     {
1867         kDebug() << "recover filename";
1868         const int argumentOffsetSize = dccArguments.size() - offset;
1869         for (int i = 0; i < argumentOffsetSize; ++i)
1870         {
1871             fileName += dccArguments.at(i);
1872             //if not last element, append a space
1873             if (i < (argumentOffsetSize - 1))
1874             {
1875                 fileName += ' ';
1876             }
1877         }
1878     }
1879     else
1880     {
1881         fileName = dccArguments.at(0);
1882     }
1883
1884     return cleanDccFileName(fileName);
1885 }
1886
1887 QString Server::cleanDccFileName(const QString& filename) const
1888 {
1889     QString cleanFileName = filename;
1890
1891     //we want a clean filename to get rid of the mass """filename"""
1892     //NOTE: if a filename starts really with a ", it is escaped -> \" (2 chars)
1893     //      but most clients don't support that and just replace it with a _
1894     while (cleanFileName.startsWith('\"') && cleanFileName.endsWith('\"'))
1895     {
1896         cleanFileName = cleanFileName.mid(1, cleanFileName.length() - 2);
1897     }
1898
1899     return cleanFileName;
1900 }
1901
1902 quint16 Server::stringToPort(const QString &port, bool *ok)
1903 {
1904     bool toUintOk = false;
1905     uint uPort32 = port.toUInt(&toUintOk);
1906     // ports over 65535 are invalid, someone sends us bad data
1907     if (!toUintOk || uPort32 > USHRT_MAX)
1908     {
1909         if (ok)
1910         {
1911             *ok = false;
1912         }
1913     }
1914     else
1915     {
1916         if (ok)
1917         {
1918             *ok = true;
1919         }
1920     }
1921     return (quint16)uPort32;
1922 }
1923
1924 QString Server::recipientNick() const
1925 {
1926     QStringList nickList;
1927
1928     // fill nickList with all nicks we know about
1929     foreach (Channel* lookChannel, m_channelList)
1930     {
1931         foreach (Nick* lookNick, lookChannel->getNickList())
1932         {
1933             if (!nickList.contains(lookNick->getChannelNick()->getNickname()))
1934                 nickList.append(lookNick->getChannelNick()->getNickname());
1935         }
1936     }
1937
1938     // add Queries as well, but don't insert duplicates
1939     foreach (Query* lookQuery, m_queryList)
1940     {
1941         if(!nickList.contains(lookQuery->getName())) nickList.append(lookQuery->getName());
1942     }
1943     QStringListModel model;
1944     model.setStringList(nickList);
1945     return DCC::RecipientDialog::getNickname(getViewContainer()->getWindow(), &model);
1946 }
1947
1948 void Server::addDccGet(const QString &sourceNick, const QStringList &dccArguments)
1949 {
1950     //filename ip port filesize [token]
1951     QString ip;
1952     quint16 port;
1953     QString fileName;
1954     quint64 fileSize;
1955     QString token;
1956     const int argumentSize = dccArguments.count();
1957     bool ok = true;
1958
1959     if (dccArguments.at(argumentSize - 3) == "0") //port==0, for passive send, ip can't be 0
1960     {
1961         //filename ip port(0) filesize token
1962         fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token
1963         ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //-1 index, -1 token, -1 port, -1 filesize
1964         port = 0;
1965         fileSize = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token
1966         token = dccArguments.at(argumentSize - 1); //-1 index
1967     }
1968     else
1969     {
1970         //filename ip port filesize
1971         ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 3) ); //-1 index, -1 filesize
1972         fileName = recoverDccFileName(dccArguments, 3); //ip port filesize
1973         fileSize = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index
1974         port = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize
1975     }
1976
1977     if (!ok)
1978     {
1979         appendMessageToFrontmost(i18n("Error"),
1980                                  i18nc("%1=nickname","Received invalid DCC SEND request from %1.",
1981                                        sourceNick));
1982         return;
1983     }
1984
1985     DCC::TransferRecv* newDcc = Application::instance()->getDccTransferManager()->newDownload();
1986
1987     newDcc->setConnectionId(connectionId());
1988     newDcc->setPartnerNick(sourceNick);
1989
1990     newDcc->setPartnerIp(ip);
1991     newDcc->setPartnerPort(port);
1992     newDcc->setFileName(fileName);
1993     newDcc->setFileSize(fileSize);
1994     // Reverse DCC
1995     if (!token.isEmpty())
1996     {
1997         newDcc->setReverse(true, token);
1998     }
1999
2000     kDebug() << "ip: " << ip;
2001     kDebug() << "port: " << port;
2002     kDebug() << "filename: " << fileName;
2003     kDebug() << "filesize: " << fileSize;
2004     kDebug() << "token: " << token;
2005
2006     //emit after data was set
2007     emit addDccPanel();
2008
2009     if ( newDcc->queue() )
2010     {
2011         appendMessageToFrontmost( i18n( "DCC" ),
2012                                   i18n( "%1 offers to send you \"%2\" (%3)...",
2013                                         newDcc->getPartnerNick(),
2014                                         fileName,
2015                                         ( newDcc->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( newDcc->getFileSize() ) ) );
2016
2017         if (Preferences::self()->dccAutoGet())
2018             newDcc->start();
2019     }
2020 }
2021
2022 void Server::addDccChat(const QString& sourceNick, const QStringList& dccArguments)
2023 {
2024     //chat ip port [token]
2025     QString ip;
2026     quint16 port = 0;
2027     QString token;
2028     bool reverse = false;
2029     const int argumentSize = dccArguments.count();
2030     bool ok = true;
2031     QString extension;
2032
2033     extension = dccArguments.at(0);
2034     ip = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1));
2035
2036     if (argumentSize == 3)
2037     {
2038         //extension ip port
2039         port = stringToPort(dccArguments.at(2), &ok);
2040     }
2041     else if (argumentSize == 4)
2042     {
2043         //extension ip port(0) token
2044         token = dccArguments.at(3);
2045         reverse = true;
2046     }
2047
2048     if (!ok)
2049     {
2050         appendMessageToFrontmost(i18n("Error"),
2051                                  i18nc("%1=nickname","Received invalid DCC CHAT request from %1.",
2052                                        sourceNick));
2053         return;
2054     }
2055
2056     DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat();
2057
2058     newChat->setConnectionId(connectionId());
2059     newChat->setPartnerNick(sourceNick);
2060     newChat->setOwnNick(getNickname());
2061
2062     kDebug() << "ip: " << ip;
2063     kDebug() << "port: " << port;
2064     kDebug() << "token: " << token;
2065     kDebug() << "extension: " << extension;
2066
2067     newChat->setPartnerIp(ip);
2068     newChat->setPartnerPort(port);
2069     newChat->setReverse(reverse, token);
2070     newChat->setSelfOpened(false);
2071     newChat->setExtension(extension);
2072
2073     emit addDccChat(newChat);
2074     newChat->start();
2075 }
2076
2077 void Server::openDccChat(const QString& nickname)
2078 {
2079     kDebug();
2080     QString recipient(nickname);
2081     // if we don't have a recipient yet, let the user select one
2082     if (recipient.isEmpty())
2083     {
2084         recipient = recipientNick();
2085     }
2086
2087     // do we have a recipient *now*?
2088     if (!recipient.isEmpty())
2089     {
2090         DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat();
2091         newChat->setConnectionId(connectionId());
2092         newChat->setPartnerNick(recipient);
2093         newChat->setOwnNick(getNickname());
2094         newChat->setSelfOpened(true);
2095         emit addDccChat(newChat);
2096         newChat->start();
2097     }
2098 }
2099
2100 void Server::openDccWBoard(const QString& nickname)
2101 {
2102     kDebug();
2103     QString recipient(nickname);
2104     // if we don't have a recipient yet, let the user select one
2105     if (recipient.isEmpty())
2106     {
2107         recipient = recipientNick();
2108     }
2109
2110     // do we have a recipient *now*?
2111     if (!recipient.isEmpty())
2112     {
2113         DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat();
2114         newChat->setConnectionId(connectionId());
2115         newChat->setPartnerNick(recipient);
2116         newChat->setOwnNick(getNickname());
2117         // Set extension before emiting addDccChat
2118         newChat->setExtension(DCC::Chat::Whiteboard);
2119         newChat->setSelfOpened(true);
2120         emit addDccChat(newChat);
2121         newChat->start();
2122     }
2123 }
2124
2125 void Server::requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort)
2126 {
2127     Konversation::OutputFilterResult result = getOutputFilter()->requestDccChat(partnerNick, extension, numericalOwnIp,ownPort);
2128     queue(result.toServer);
2129 }
2130
2131 void Server::acceptDccGet(const QString& nick, const QString& file)
2132 {
2133     Application::instance()->getDccTransferManager()->acceptDccGet(m_connectionId, nick, file);
2134 }
2135
2136 void Server::dccSendRequest(const QString &partner, const QString &fileName, const QString &address, quint16 port, quint64 size)
2137 {
2138     Konversation::OutputFilterResult result = getOutputFilter()->sendRequest(partner,fileName,address,port,size);
2139     queue(result.toServer);
2140
2141     appendMessageToFrontmost( i18n( "DCC" ),
2142                               i18n( "Asking %1 to accept upload of \"%2\" (%3)...",
2143                                     partner,
2144                                     cleanDccFileName(fileName),
2145                                     ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) );
2146 }
2147
2148 void Server::dccPassiveSendRequest(const QString& recipient,const QString& fileName,const QString& address,quint64 size,const QString& token)
2149 {
2150     Konversation::OutputFilterResult result = getOutputFilter()->passiveSendRequest(recipient,fileName,address,size,token);
2151     queue(result.toServer);
2152
2153     appendMessageToFrontmost( i18n( "DCC" ),
2154                               i18n( "Asking %1 to accept passive upload of \"%2\" (%3)...",
2155                                     recipient,
2156                                     cleanDccFileName(fileName),
2157                                     ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) );
2158 }
2159
2160 void Server::dccPassiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token)
2161 {
2162     Konversation::OutputFilterResult result = getOutputFilter()->passiveChatRequest(recipient, extension, address, token);
2163     queue(result.toServer);
2164
2165     appendMessageToFrontmost(i18n("DCC"),
2166                              i18nc("%1=name, %2=dcc extension, chat or wboard for example","Asking %1 to accept %2...", recipient, extension));
2167 }
2168
2169 void Server::dccPassiveResumeGetRequest(const QString& sender,const QString& fileName,quint16 port,KIO::filesize_t startAt,const QString &token)
2170 {
2171     Konversation::OutputFilterResult result = getOutputFilter()->resumePassiveRequest(sender,fileName,port,startAt,token);;
2172     queue(result.toServer);
2173 }
2174
2175 void Server::dccResumeGetRequest(const QString &sender, const QString &fileName, quint16 port, KIO::filesize_t startAt)
2176 {
2177     Konversation::OutputFilterResult result = getOutputFilter()->resumeRequest(sender,fileName,port,startAt);;
2178     queue(result.toServer);
2179 }
2180
2181 void Server::dccReverseSendAck(const QString& partnerNick,const QString& fileName,const QString& ownAddress,quint16 ownPort,quint64 size,const QString& reverseToken)
2182 {
2183     Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveSendRequest(partnerNick,fileName,ownAddress,ownPort,size,reverseToken);
2184     queue(result.toServer);
2185 }
2186
2187 void Server::dccReverseChatAck(const QString& partnerNick, const QString& extension, const QString& ownAddress, quint16 ownPort, const QString& reverseToken)
2188 {
2189     Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveChatRequest(partnerNick, extension, ownAddress, ownPort, reverseToken);
2190     queue(result.toServer);
2191 }
2192
2193 void Server::dccRejectSend(const QString& partnerNick, const QString& fileName)
2194 {
2195     Konversation::OutputFilterResult result = getOutputFilter()->rejectDccSend(partnerNick,fileName);
2196     queue(result.toServer);
2197 }
2198
2199 void Server::dccRejectChat(const QString& partnerNick, const QString& extension)
2200 {
2201     Konversation::OutputFilterResult result = getOutputFilter()->rejectDccChat(partnerNick, extension);
2202     queue(result.toServer);
2203 }
2204
2205 void Server::startReverseDccChat(const QString &sourceNick, const QStringList &dccArguments)
2206 {
2207     kDebug();
2208     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2209
2210     bool ok = true;
2211     QString partnerIP = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1));
2212     quint16 port = stringToPort(dccArguments.at(2), &ok);
2213     QString token = dccArguments.at(3);
2214
2215     kDebug() << "ip: " << partnerIP;
2216     kDebug() << "port: " << port;
2217     kDebug() << "token: " << token;
2218
2219     if (!ok || dtm->startReverseChat(connectionId(), sourceNick,
2220                                     partnerIP, port, token) == 0)
2221     {
2222         // DTM could not find a matched item
2223         appendMessageToFrontmost(i18n("Error"),
2224                                  i18nc("%1 = nickname",
2225                                        "Received invalid passive DCC chat acceptance message from %1.",
2226                                        sourceNick));
2227     }
2228 }
2229
2230 void Server::startReverseDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments)
2231 {
2232     kDebug();
2233     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2234
2235     bool ok = true;
2236     const int argumentSize = dccArguments.size();
2237     QString partnerIP = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //dccArguments[1] ) );
2238     quint16 port = stringToPort(dccArguments.at(argumentSize - 3), &ok);
2239     QString token = dccArguments.at(argumentSize - 1);
2240     quint64 fileSize = dccArguments.at(argumentSize - 2).toULongLong();
2241     QString fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token
2242
2243     kDebug() << "ip: " << partnerIP;
2244     kDebug() << "port: " << port;
2245     kDebug() << "filename: " << fileName;
2246     kDebug() << "filesize: " << fileSize;
2247     kDebug() << "token: " << token;
2248
2249     if (!ok ||
2250         dtm->startReverseSending(connectionId(), sourceNick,
2251                                  fileName,  // filename
2252                                  partnerIP,  // partner IP
2253                                  port,  // partner port
2254                                  fileSize,  // filesize
2255                                  token  // Reverse DCC token
2256          ) == 0)
2257     {
2258         // DTM could not find a matched item
2259         appendMessageToFrontmost(i18n("Error"),
2260                                  i18nc("%1 = file name, %2 = nickname",
2261                                        "Received invalid passive DCC send acceptance message for \"%1\" from %2.",
2262                                        fileName,
2263                                        sourceNick));
2264     }
2265 }
2266
2267 void Server::resumeDccGetTransfer(const QString &sourceNick, const QStringList &dccArguments)
2268 {
2269     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2270
2271     //filename port position [token]
2272     QString fileName;
2273     quint64 position;
2274     quint16 ownPort;
2275     bool ok = true;
2276     const int argumentSize = dccArguments.count();
2277
2278     if (dccArguments.at(argumentSize - 3) == "0") //-1 index, -1 token, -1 pos
2279     {
2280         fileName = recoverDccFileName(dccArguments, 3); //port position token
2281         ownPort = 0;
2282         position = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token
2283     }
2284     else
2285     {
2286         fileName = recoverDccFileName(dccArguments, 2); //port position
2287         ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 pos
2288         position = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index
2289     }
2290     //do we need the token here?
2291
2292     DCC::TransferRecv* dccTransfer = 0;
2293     if (ok)
2294     {
2295         dccTransfer = dtm->resumeDownload(connectionId(), sourceNick, fileName, ownPort, position);
2296     }
2297
2298     if (dccTransfer)
2299     {
2300         appendMessageToFrontmost(i18n("DCC"),
2301                                  i18nc("%1 = file name, %2 = nickname of sender, %3 = percentage of file size, %4 = file size",
2302                                        "Resuming download of \"%1\" from %2 starting at %3% of %4...",
2303                                        fileName,
2304                                        sourceNick,
2305                                        QString::number( dccTransfer->getProgress()),
2306                                        (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize())));
2307     }
2308     else
2309     {
2310         appendMessageToFrontmost(i18n("Error"),
2311                                  i18nc("%1 = file name, %2 = nickname",
2312                                        "Received invalid resume acceptance message for \"%1\" from %2.",
2313                                        fileName,
2314                                        sourceNick));
2315     }
2316 }
2317
2318 void Server::resumeDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments)
2319 {
2320     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2321
2322     bool passiv = false;
2323     QString fileName;
2324     quint64 position;
2325     QString token;
2326     quint16 ownPort;
2327     bool ok = true;
2328     const int argumentSize = dccArguments.count();
2329
2330     //filename port filepos [token]
2331     if (dccArguments.at( argumentSize - 3) == "0")
2332     {
2333         //filename port filepos token
2334         passiv = true;
2335         ownPort = 0;
2336         token = dccArguments.at( argumentSize - 1); // -1 index
2337         position = dccArguments.at( argumentSize - 2).toULongLong(); // -1 index, -1 token
2338         fileName = recoverDccFileName(dccArguments, 3); //port filepos token
2339     }
2340     else
2341     {
2342         //filename port filepos
2343         ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize
2344         position = dccArguments.at( argumentSize - 1).toULongLong(); // -1 index
2345         fileName = recoverDccFileName(dccArguments, 2); //port filepos
2346     }
2347
2348     DCC::TransferSend* dccTransfer = 0;
2349     if (ok)
2350     {
2351         dccTransfer = dtm->resumeUpload(connectionId(), sourceNick, fileName, ownPort, position);
2352     }
2353
2354     if (dccTransfer)
2355     {
2356         appendMessageToFrontmost(i18n("DCC"),
2357                                  i18nc("%1 = file name, %2 = nickname of recipient, %3 = percentage of file size, %4 = file size",
2358                                        "Resuming upload of \"%1\" to %2 starting at %3% of %4...",
2359                                        fileName,
2360                                        sourceNick,
2361                                        QString::number(dccTransfer->getProgress()),
2362                                        (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize())));
2363
2364         // fileName can't have " here
2365         if (fileName.contains(' '))
2366             fileName = '\"'+fileName+'\"';
2367
2368         // FIXME: this operation should be done by TransferManager
2369         Konversation::OutputFilterResult result;
2370         if (passiv)
2371             result = getOutputFilter()->acceptPassiveResumeRequest( sourceNick, fileName, ownPort, position, token );
2372         else
2373             result = getOutputFilter()->acceptResumeRequest( sourceNick, fileName, ownPort, position );
2374         queue( result.toServer );
2375     }
2376     else
2377     {
2378         appendMessageToFrontmost(i18n("Error"),
2379                                  i18nc("%1 = file name, %2 = nickname",
2380                                        "Received invalid resume request for \"%1\" from %2.",
2381                                        fileName,
2382                                        sourceNick));
2383     }
2384 }
2385
2386 void Server::rejectDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments)
2387 {
2388     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2389
2390     //filename
2391     QString fileName = recoverDccFileName(dccArguments, 0);
2392
2393     DCC::TransferSend* dccTransfer = dtm->rejectSend(connectionId(), sourceNick, fileName);
2394
2395     if (!dccTransfer)
2396     {
2397         appendMessageToFrontmost(i18n("Error"),
2398                                  i18nc("%1 = file name, %2 = nickname",
2399                                        "Received invalid reject request for \"%1\" from %2.",
2400                                        fileName,
2401                                        sourceNick));
2402     }
2403 }
2404
2405 void Server::rejectDccChat(const QString& sourceNick)
2406 {
2407     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2408
2409     DCC::Chat* dccChat = dtm->rejectChat(connectionId(), sourceNick);
2410
2411     if (!dccChat)
2412     {
2413         appendMessageToFrontmost(i18n("Error"),
2414                                  i18nc("%1 = nickname",
2415                                        "Received invalid reject request from %1.",
2416                                        sourceNick));
2417     }
2418 }
2419
2420 void Server::dccGetDone(DCC::Transfer* item)
2421 {
2422     if (!item)
2423         return;
2424
2425     if(item->getStatus() == DCC::Transfer::Done)
2426     {
2427         appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender",
2428             "Download of \"%1\" from %2 finished.", item->getFileName(), item->getPartnerNick()));
2429     }
2430     else if(item->getStatus() == DCC::Transfer::Failed)
2431     {
2432         appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender",
2433             "Download of \"%1\" from %2 failed. Reason: %3.", item->getFileName(),
2434             item->getPartnerNick(), item->getStatusDetail()));
2435     }
2436 }
2437
2438 void Server::dccSendDone(DCC::Transfer* item)
2439 {
2440     if (!item)
2441         return;
2442
2443     if(item->getStatus() == DCC::Transfer::Done)
2444         appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient",
2445             "Upload of \"%1\" to %2 finished.", item->getFileName(), item->getPartnerNick()));
2446     else if(item->getStatus() == DCC::Transfer::Failed)
2447         appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient",
2448             "Upload of \"%1\" to %2 failed. Reason: %3.", item->getFileName(), item->getPartnerNick(),
2449             item->getStatusDetail()));
2450 }
2451
2452 void Server::dccStatusChanged(DCC::Transfer *item, int newStatus, int oldStatus)
2453 {
2454     if(!item)
2455         return;
2456
2457     if ( item->getType() == DCC::Transfer::Send )
2458     {
2459         // when resuming, a message about the receiver's acceptance has been shown already, so suppress this message
2460         if ( newStatus == DCC::Transfer::Transferring && oldStatus == DCC::Transfer::WaitingRemote && !item->isResumed() )
2461             appendMessageToFrontmost( i18n( "DCC" ), i18nc( "%1 = file name, %2 nickname of recipient",
2462                 "Sending \"%1\" to %2...", item->getFileName(), item->getPartnerNick() ) );
2463     }
2464     else  // type == Receive
2465     {
2466         if ( newStatus == DCC::Transfer::Transferring && !item->isResumed() )
2467         {
2468             appendMessageToFrontmost( i18n( "DCC" ),
2469                                         i18nc( "%1 = file name, %2 = file size, %3 = nickname of sender", "Downloading \"%1\" (%2) from %3...",
2470                                               item->getFileName(),
2471                                             ( item->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( item->getFileSize() ),
2472                                             item->getPartnerNick() ) );
2473         }
2474     }
2475 }
2476
2477 void Server::removeQuery(Query* query)
2478 {
2479     m_queryList.removeOne(query);
2480     query->deleteLater();
2481 }
2482
2483 void Server::sendJoinCommand(const QString& name, const QString& password)
2484 {
2485     Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),
2486         Preferences::self()->commandChar() + "JOIN " + name + ' ' + password, QString());
2487     queue(result.toServer);
2488 }
2489
2490 void Server::joinChannel(const QString& name, const QString& hostmask)
2491 {
2492     // (re-)join channel, open a new panel if needed
2493     Channel* channel = getChannelByName(name);
2494
2495     if (!channel)
2496     {
2497         channel=getViewContainer()->addChannel(this,name);
2498         Q_ASSERT(channel);
2499         channel->setNickname(getNickname());
2500         channel->indicateAway(m_away);
2501
2502         if (getServerGroup())
2503         {
2504             Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(name);
2505             channel->setNotificationsEnabled(channelSettings.enableNotifications());
2506             getServerGroup()->appendChannelHistory(channelSettings);
2507         }
2508
2509         m_channelList.append(channel);
2510
2511         connect(channel,SIGNAL (sendFile()),this,SLOT (requestDccSend()) );
2512         connect(this, SIGNAL(nicknameChanged(const QString&)), channel, SLOT(setNickname(const QString&)));
2513     }
2514     // Move channel from unjoined (if present) to joined list and add our own nickname to the joined list.
2515     ChannelNickPtr channelNick = addNickToJoinedChannelsList(name, getNickname());
2516
2517     if ((channelNick->getHostmask() != hostmask ) && !hostmask.isEmpty())
2518     {
2519         NickInfoPtr nickInfo = channelNick->getNickInfo();
2520         nickInfo->setHostmask(hostmask);
2521     }
2522
2523     channel->joinNickname(channelNick);
2524 }
2525
2526 void Server::removeChannel(Channel* channel)
2527 {
2528     // Update NickInfo.
2529     removeJoinedChannel(channel->getName());
2530
2531     if (getServerGroup())
2532     {
2533         Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(channel->getName());
2534         channelSettings.setNotificationsEnabled(channel->notificationsEnabled());
2535         getServerGroup()->appendChannelHistory(channelSettings);
2536     }
2537
2538     m_channelList.removeOne(channel);
2539 }
2540
2541 void Server::updateChannelMode(const QString &updater, const QString &channelName, char mode, bool plus, const QString &parameter)
2542 {
2543
2544     Channel* channel=getChannelByName(channelName);
2545
2546     if(channel)                                   //Let the channel be verbose to the screen about the change, and update channelNick
2547         channel->updateMode(updater, mode, plus, parameter);
2548     // TODO: What is mode character for owner?
2549     // Answer from JOHNFLUX - I think that admin is the same as owner.  Channel.h has owner as "a"
2550     // "q" is the likely answer.. UnrealIRCd and euIRCd use it.
2551     // TODO these need to become dynamic
2552     QString userModes="vhoqa";                    // voice halfop op owner admin
2553     int modePos = userModes.indexOf(mode);
2554     if (modePos > 0)
2555     {
2556         ChannelNickPtr updateeNick = getChannelNick(channelName, parameter);
2557         if(!updateeNick)
2558         {
2559 /*
2560             if(parameter.isEmpty())
2561             {
2562                 kDebug() << "in updateChannelMode, a nick with no-name has had their mode '" << mode << "' changed to (" <<plus << ") in channel '" << channelName << "' by " << updater << ".  How this happened, I have no idea.  Please report this message to irc #konversation if you want to be helpful." << endl << "Ignoring the error and continuing.";
2563                                                   //this will get their attention.
2564                 kDebug() << kBacktrace();
2565             }
2566             else
2567             {
2568                 kDebug() << "in updateChannelMode, could not find updatee nick " << parameter << " for channel " << channelName;
2569                 kDebug() << "This could indicate an obscure race condition that is safely being handled (like the mode of someone changed and they quit almost simulatanously, or it could indicate an internal error.";
2570             }
2571 */
2572             //TODO Do we need to add this nick?
2573             return;
2574         }
2575
2576         updateeNick->setMode(mode, plus);
2577
2578         // Note that channel will be moved to joined list if necessary.
2579         addNickToJoinedChannelsList(channelName, parameter);
2580     }
2581
2582     // Update channel ban list.
2583     if (mode == 'b')
2584     {
2585         if (plus)
2586         {
2587             QDateTime when;
2588             addBan(channelName, QString("%1 %2 %3").arg(parameter).arg(updater).arg(QDateTime::currentDateTime().toTime_t()));
2589         } else {
2590             removeBan(channelName, parameter);
2591         }
2592     }
2593 }
2594
2595 void Server::updateChannelModeWidgets(const QString &channelName, char mode, const QString &parameter)
2596 {
2597     Channel* channel=getChannelByName(channelName);
2598     if(channel) channel->updateModeWidgets(mode,true,parameter);
2599 }
2600
2601 void Server::updateChannelQuickButtons()
2602 {
2603     foreach (Channel* channel, m_channelList)
2604     {
2605         channel->updateQuickButtons(Preferences::quickButtonList());
2606     }
2607 }
2608
2609 Channel* Server::getChannelByName(const QString& name)
2610 {
2611     // Convert wanted channel name to lowercase
2612     QString wanted = name.toLower();
2613
2614     // Traverse through list to find the channel named "name"
2615     foreach (Channel* lookChannel, m_channelList)
2616     {
2617         if (lookChannel->getName().toLower()==wanted) return lookChannel;
2618     }
2619     // No channel by that name found? Return 0. Happens on first channel join
2620     return 0;
2621 }
2622
2623 Query* Server::getQueryByName(const QString& name)
2624 {
2625     // Convert wanted query name to lowercase
2626     QString wanted = name.toLower();
2627
2628     // Traverse through list to find the query with "name"
2629     foreach (Query* lookQuery, m_queryList)
2630     {
2631         if(lookQuery->getName().toLower()==wanted) return lookQuery;
2632     }
2633     // No query by that name found? Must be a new query request. Return 0
2634     return 0;
2635 }
2636
2637 void Server::resetNickList(const QString& channelName)
2638 {
2639     Channel* outChannel=getChannelByName(channelName);
2640     if(outChannel) outChannel->resetNickList();
2641 }
2642
2643 void Server::addPendingNickList(const QString& channelName,const QStringList& nickList)
2644 {
2645     Channel* outChannel=getChannelByName(channelName);
2646     if(outChannel) outChannel->addPendingNickList(nickList);
2647 }
2648
2649 // Adds a nickname to the joinedChannels list.
2650 // Creates new NickInfo if necessary.
2651 // If needed, moves the channel from the unjoined list to the joined list.
2652 // Returns the NickInfo for the nickname.
2653 ChannelNickPtr Server::addNickToJoinedChannelsList(const QString& channelName, const QString& nickname)
2654 {
2655     bool doChannelJoinedSignal = false;
2656     bool doWatchedNickChangedSignal = false;
2657     bool doChannelMembersChangedSignal = false;
2658     QString lcNickname(nickname.toLower());
2659     // Create NickInfo if not already created.
2660     NickInfoPtr nickInfo = getNickInfo(nickname);
2661     if (!nickInfo)
2662     {
2663         nickInfo = new NickInfo(nickname, this);
2664         m_allNicks.insert(lcNickname, nickInfo);
2665         doWatchedNickChangedSignal = isWatchedNick(nickname);
2666     }
2667     // if nickinfo already exists update nickname, in case we created the nickinfo based
2668     // on e.g. an incorrectly capitalized ISON request
2669     else
2670         nickInfo->setNickname(nickname);
2671
2672     // Move the channel from unjoined list (if present) to joined list.
2673     QString lcChannelName = channelName.toLower();
2674     ChannelNickMap *channel;
2675     if (m_unjoinedChannels.contains(lcChannelName))
2676     {
2677         channel = m_unjoinedChannels[lcChannelName];
2678         m_unjoinedChannels.remove(lcChannelName);
2679         m_joinedChannels.insert(lcChannelName, channel);
2680         doChannelJoinedSignal = true;
2681     }
2682     else
2683     {
2684         // Create a new list in the joined channels if not already present.
2685         if (!m_joinedChannels.contains(lcChannelName))
2686         {
2687             channel = new ChannelNickMap;
2688             m_joinedChannels.insert(lcChannelName, channel);
2689             doChannelJoinedSignal = true;
2690         }
2691         else
2692             channel = m_joinedChannels[lcChannelName];
2693     }
2694     // Add NickInfo to channel list if not already in the list.
2695     ChannelNickPtr channelNick;
2696     if (!channel->contains(lcNickname))
2697     {
2698         channelNick = new ChannelNick(nickInfo, lcChannelName);
2699         Q_ASSERT(channelNick);
2700         channel->insert(lcNickname, channelNick);
2701         doChannelMembersChangedSignal = true;
2702     }
2703     channelNick = (*channel)[lcNickname];
2704     Q_ASSERT(channelNick);                        //Since we just added it if it didn't exist, it should be guaranteed to exist now
2705     if (doWatchedNickChangedSignal) emit watchedNickChanged(this, nickname, true);
2706     if (doChannelJoinedSignal) emit channelJoinedOrUnjoined(this, channelName, true);
2707     if (doChannelMembersChangedSignal) emit channelMembersChanged(this, channelName, true, false, nickname);
2708     return channelNick;
2709 }
2710
2711 // Adds a nickname to the unjoinedChannels list.
2712 // Creates new NickInfo if necessary.
2713 // If needed, moves the channel from the joined list to the unjoined list.
2714 // If mode != 99 sets the mode for this nick in this channel.
2715 // Returns the NickInfo for the nickname.
2716 ChannelNickPtr Server::addNickToUnjoinedChannelsList(const QString& channelName, const QString& nickname)
2717 {
2718     bool doChannelUnjoinedSignal = false;
2719     bool doWatchedNickChangedSignal = false;
2720     bool doChannelMembersChangedSignal = false;
2721     QString lcNickname(nickname.toLower());
2722     // Create NickInfo if not already created.
2723     NickInfoPtr nickInfo = getNickInfo(nickname);
2724     if (!nickInfo)
2725     {
2726         nickInfo = new NickInfo(nickname, this);
2727         m_allNicks.insert(lcNickname, nickInfo);
2728         doWatchedNickChangedSignal = isWatchedNick(nickname);
2729     }
2730     // Move the channel from joined list (if present) to unjoined list.
2731     QString lcChannelName = channelName.toLower();
2732     ChannelNickMap *channel;
2733     if (m_joinedChannels.contains(lcChannelName))
2734     {
2735         channel = m_joinedChannels[lcChannelName];
2736         m_joinedChannels.remove(lcChannelName);
2737         m_unjoinedChannels.insert(lcChannelName, channel);
2738         doChannelUnjoinedSignal = true;
2739     }
2740     else
2741     {
2742         // Create a new list in the unjoined channels if not already present.
2743         if (!m_unjoinedChannels.contains(lcChannelName))
2744         {
2745             channel = new ChannelNickMap;
2746             m_unjoinedChannels.insert(lcChannelName, channel);
2747             doChannelUnjoinedSignal = true;
2748         }
2749         else
2750             channel = m_unjoinedChannels[lcChannelName];
2751     }
2752     // Add NickInfo to unjoinedChannels list if not already in the list.
2753     ChannelNickPtr channelNick;
2754     if (!channel->contains(lcNickname))
2755     {
2756         channelNick = new ChannelNick(nickInfo, lcChannelName);
2757         channel->insert(lcNickname, channelNick);
2758         doChannelMembersChangedSignal = true;
2759     }
2760     channelNick = (*channel)[lcNickname];
2761     // Set the mode for the nick in this channel.
2762     if (doWatchedNickChangedSignal) emit watchedNickChanged(this, nickname, true);
2763     if (doChannelUnjoinedSignal) emit channelJoinedOrUnjoined(this, channelName, false);
2764     if (doChannelMembersChangedSignal) emit channelMembersChanged(this, channelName, false, false, nickname);
2765     return channelNick;
2766 }
2767
2768 /**
2769  * If not already online, changes a nick to the online state by creating
2770  * a NickInfo for it and emits various signals and messages for it.
2771  * This method should only be called for nicks on the watch list.
2772  * @param nickname           The nickname that is online.
2773  * @return                   Pointer to NickInfo for nick.
2774  */
2775 NickInfoPtr Server::setWatchedNickOnline(const QString& nickname)
2776 {
2777     NickInfoPtr nickInfo = getNickInfo(nickname);
2778     if (!nickInfo)
2779     {
2780         QString lcNickname(nickname.toLower());
2781         nickInfo = new NickInfo(nickname, this);
2782         m_allNicks.insert(lcNickname, nickInfo);
2783     }
2784
2785     emit watchedNickChanged(this, nickname, true);
2786     KABC::Addressee addressee = nickInfo->getAddressee();
2787     if (!addressee.isEmpty()) Konversation::Addressbook::self()->emitContactPresenceChanged(addressee.uid());
2788
2789     appendMessageToFrontmost(i18n("Notify"),"<a class=\"nick\" href=\"#"+nickname+"\">"+
2790         i18n("%1 is online (%2).", nickname, getServerName())+"</a>", getStatusView());
2791
2792     static_cast<Application*>(kapp)->notificationHandler()->nickOnline(getStatusView(), nickname);
2793
2794     nickInfo->setPrintedOnline(true);
2795     return nickInfo;
2796 }
2797
2798 void Server::setWatchedNickOffline(const QString& nickname, const NickInfoPtr nickInfo)
2799 {
2800    if (nickInfo) {
2801         KABC::Addressee addressee = nickInfo->getAddressee();
2802         if (!addressee.isEmpty()) Konversation::Addressbook::self()->emitContactPresenceChanged(addressee.uid(), 1);
2803     }
2804
2805     emit watchedNickChanged(this, nickname, false);
2806
2807     appendMessageToFrontmost(i18n("Notify"), i18n("%1 went offline (%2).", nickname, getServerName()), getStatusView());
2808
2809     static_cast<Application*>(kapp)->notificationHandler()->nickOffline(getStatusView(), nickname);
2810
2811 }
2812
2813 bool Server::setNickOffline(const QString& nickname)
2814 {
2815     QString lcNickname(nickname.toLower());
2816     NickInfoPtr nickInfo = getNickInfo(lcNickname);
2817
2818     bool wasOnline = nickInfo ? nickInfo->getPrintedOnline() : false;
2819
2820     if (wasOnline)
2821     {
2822         // Delete from query list, if present.
2823         if (m_queryNicks.contains(lcNickname)) m_queryNicks.remove(lcNickname);
2824         // Delete the nickname from all channels (joined or unjoined).
2825         QStringList nickChannels = getNickChannels(lcNickname);
2826         QStringList::iterator itEnd = nickChannels.end();
2827
2828         for(QStringList::iterator it = nickChannels.begin(); it != itEnd; ++it)
2829         {
2830             QString channel = (*it);
2831             removeChannelNick(channel, lcNickname);
2832         }
2833
2834         // Delete NickInfo.
2835         if (m_allNicks.contains(lcNickname)) m_allNicks.remove(lcNickname);
2836         // If the nick was in the watch list, emit various signals and messages.
2837         if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, nickInfo);
2838
2839         nickInfo->setPrintedOnline(false);
2840     }
2841
2842     return (!nickInfo.isNull());
2843 }
2844
2845 /**
2846  * If nickname is no longer on any channel list, or the query list, delete it altogether.
2847  * Call this routine only if the nick is not on the notify list or is on the notify
2848  * list but is known to be offline.
2849  * @param nickname           The nickname to be deleted.  Case insensitive.
2850  * @return                   True if the nickname is deleted.
2851  */
2852 bool Server::deleteNickIfUnlisted(const QString &nickname)
2853 {
2854     QString lcNickname(nickname.toLower());
2855     // Don't delete our own nickinfo.
2856     if (lcNickname == loweredNickname()) return false;
2857
2858     if (!m_queryNicks.contains(lcNickname))
2859     {
2860         QStringList nickChannels = getNickChannels(nickname);
2861         if (nickChannels.isEmpty())
2862         {
2863             m_allNicks.remove(lcNickname);
2864             return true;
2865         }
2866     }
2867     return false;
2868 }
2869
2870 /**
2871  * Remove nickname from a channel (on joined or unjoined lists).
2872  * @param channelName The channel name.  Case insensitive.
2873  * @param nickname    The nickname.  Case insensitive.
2874  */
2875 void Server::removeChannelNick(const QString& channelName, const QString& nickname)
2876 {
2877     bool doSignal = false;
2878     bool joined = false;
2879     QString lcChannelName = channelName.toLower();
2880     QString lcNickname = nickname.toLower();
2881     ChannelNickMap *channel;
2882     if (m_joinedChannels.contains(lcChannelName))
2883     {
2884         channel = m_joinedChannels[lcChannelName];
2885         if (channel->contains(lcNickname))
2886         {
2887             channel->remove(lcNickname);
2888             doSignal = true;
2889             joined = true;
2890             // Note: Channel should not be empty because user's own nick should still be
2891             // in it, so do not need to delete empty channel here.
2892         }
2893         else
2894         {
2895             kDebug() << "Error: Tried to remove nickname=" << nickname << " from joined channel=" << channelName;
2896         }
2897     }
2898     else
2899     {
2900         if (m_unjoinedChannels.contains(lcChannelName))
2901         {
2902             channel = m_unjoinedChannels[lcChannelName];
2903             if (channel->contains(lcNickname))
2904             {
2905                 channel->remove(lcNickname);
2906                 doSignal = true;
2907                 joined = false;
2908                 // If channel is now empty, delete it.
2909                 // Caution: Any iterators across unjoinedChannels will be come invalid here.
2910                 if (channel->isEmpty()) m_unjoinedChannels.remove(lcChannelName);
2911             }
2912             else
2913             {
2914                 kDebug() << "Error: Tried to remove nickname=" << nickname << " from unjoined channel=" << channelName;
2915             }
2916         }
2917     }
2918     if (doSignal) emit channelMembersChanged(this, channelName, joined, true, nickname);
2919 }
2920
2921 QStringList Server::getWatchList()
2922 {
2923     // no nickinfo ISON for the time being
2924     if(getServerGroup())
2925         return Preferences::notifyListByGroupId(getServerGroup()->id());
2926     else
2927         return QStringList();
2928
2929     if (m_serverISON)
2930         return m_serverISON->getWatchList();
2931     else
2932         return QStringList();
2933 }
2934
2935 QString Server::getWatchListString() { return getWatchList().join(" "); }
2936
2937 QStringList Server::getISONList()
2938 {
2939     // no nickinfo ISON for the time being
2940     if(getServerGroup())
2941         return Preferences::notifyListByGroupId(getServerGroup()->id());
2942     else
2943         return QStringList();
2944
2945     if (m_serverISON)
2946         return m_serverISON->getISONList();
2947     else
2948         return QStringList();
2949 }
2950
2951 QString Server::getISONListString() { return getISONList().join(" "); }
2952
2953 /**
2954  * Return true if the given nickname is on the watch list.
2955  */
2956 bool Server::isWatchedNick(const QString& nickname)
2957 {
2958     // Get watch list from preferences.
2959     QString watchlist= ' ' + getWatchListString() + ' ';
2960     // Search case-insensitivly
2961     return watchlist.contains(' ' + nickname + ' ', Qt::CaseInsensitive);
2962 }
2963
2964 /**
2965  * Remove channel from the joined list, placing it in the unjoined list.
2966  * All the unwatched nicks are removed from the channel.  If the channel becomes
2967  * empty, it is deleted.
2968  * @param channelName        Name of the channel.  Case sensitive.
2969  */
2970 void Server::removeJoinedChannel(const QString& channelName)
2971 {
2972     bool doSignal = false;
2973     QStringList watchListLower = getWatchList();
2974     QString lcChannelName = channelName.toLower();
2975     // Move the channel nick list from the joined to unjoined lists.
2976     if (m_joinedChannels.contains(lcChannelName))
2977     {
2978         doSignal = true;
2979         ChannelNickMap* channel = m_joinedChannels[lcChannelName];
2980         m_joinedChannels.remove(lcChannelName);
2981         m_unjoinedChannels.insert(lcChannelName, channel);
2982         // Remove nicks not on the watch list.
2983         bool allDeleted = true;
2984         Q_ASSERT(channel);
2985         if(!channel) return;                      //already removed.. hmm
2986         ChannelNickMap::Iterator member;
2987         for ( member = channel->begin(); member != channel->end() ;)
2988         {
2989             QString lcNickname = member.key();
2990             if (!watchListLower.contains(lcNickname))
2991             {
2992                 // Remove the unwatched nickname from the unjoined channel.
2993                 channel->erase(member);
2994                 // If the nick is no longer listed in any channels or query list, delete it altogether.
2995                 deleteNickIfUnlisted(lcNickname);
2996                 member = channel->begin();
2997             }
2998             else
2999             {
3000                 allDeleted = false;
3001                 ++member;
3002             }
3003         }
3004         // If all were deleted, remove the channel from the unjoined list.
3005         if (allDeleted)
3006         {
3007             channel = m_unjoinedChannels[lcChannelName];
3008             m_unjoinedChannels.remove(lcChannelName);
3009             delete channel;                       // recover memory!
3010         }
3011     }
3012     if (doSignal) emit channelJoinedOrUnjoined(this, channelName, false);
3013 }
3014
3015 // Renames a nickname in all NickInfo lists.
3016 // Returns pointer to the NickInfo object or 0 if nick not found.
3017 void Server::renameNickInfo(NickInfoPtr nickInfo, const QString& newname)
3018 {
3019     if (nickInfo)
3020     {
3021         // Get existing lowercase nickname and rename nickname in the NickInfo object.
3022         QString lcNickname(nickInfo->loweredNickname());
3023         nickInfo->setNickname(newname);
3024         nickInfo->setIdentified(false);
3025         QString lcNewname(newname.toLower());
3026         // Rename the key in m_allNicks list.
3027         m_allNicks.remove(lcNickname);
3028         m_allNicks.insert(lcNewname, nickInfo);
3029         // Rename key in the joined and unjoined lists.
3030         QStringList nickChannels = getNickChannels(lcNickname);
3031         QStringList::iterator itEnd = nickChannels.end();
3032
3033         for(QStringList::iterator it = nickChannels.begin(); it != itEnd; ++it)
3034         {
3035             const ChannelNickMap *channel = getChannelMembers(*it);
3036             Q_ASSERT(channel);
3037             ChannelNickPtr member = (*channel)[lcNickname];
3038             Q_ASSERT(member);
3039             const_cast<ChannelNickMap *>(channel)->remove(lcNickname);
3040             const_cast<ChannelNickMap *>(channel)->insert(lcNewname, member);
3041         }
3042
3043         // Rename key in Query list.
3044         if (m_queryNicks.contains(lcNickname))
3045         {
3046             m_queryNicks.remove(lcNickname);
3047             m_queryNicks.insert(lcNewname, nickInfo);
3048         }
3049     }
3050     else
3051     {
3052         kDebug() << "was called for newname='" << newname << "' but nickInfo is null";
3053     }