~paulbrianstewart/quassel/833751-String-Error-Fix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/***************************************************************************
 *   Copyright (C) 2005-2010 by the Quassel Project                        *
 *   devel@quassel-irc.org                                                 *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) version 3.                                           *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "ircparser.h"

#include "corenetwork.h"
#include "eventmanager.h"
#include "ircevent.h"
#include "messageevent.h"
#include "networkevent.h"

#ifdef HAVE_QCA2
#  include "cipher.h"
#endif

IrcParser::IrcParser(CoreSession *session) :
  QObject(session),
  _coreSession(session)
{

}

bool IrcParser::checkParamCount(const QString &cmd, const QList<QByteArray> &params, int minParams) {
  if(params.count() < minParams) {
    qWarning() << "Expected" << minParams << "params for IRC command" << cmd << ", got:" << params;
    return false;
  }
  return true;
}

QByteArray IrcParser::decrypt(Network *network, const QString &bufferName, const QByteArray &message, bool isTopic) {
#ifdef HAVE_QCA2
  if(message.isEmpty())
    return message;

  if(!Cipher::neededFeaturesAvailable())
    return message;

  Cipher *cipher = qobject_cast<CoreNetwork *>(network)->cipher(bufferName);
  if(!cipher)
    return message;

  return isTopic? cipher->decryptTopic(message) : cipher->decrypt(message);
#else
  Q_UNUSED(network);
  Q_UNUSED(bufferName);
  Q_UNUSED(isTopic);
  return message;
#endif
}

/* parse the raw server string and generate an appropriate event */
/* used to be handleServerMsg()                                  */
void IrcParser::processNetworkIncoming(NetworkDataEvent *e) {
  CoreNetwork *net = qobject_cast<CoreNetwork *>(e->network());
  if(!net) {
    qWarning() << "Received network event without valid network pointer!";
    return;
  }

  // note that the IRC server is still alive
  net->resetPingTimeout();

  QByteArray msg = e->data();
  if(msg.isEmpty()) {
    qWarning() << "Received empty string from server!";
    return;
  }

  // Now we split the raw message into its various parts...
  QString prefix;
  QByteArray trailing;
  QString cmd, target;

  // First, check for a trailing parameter introduced by " :", since this might screw up splitting the msg
  // NOTE: This assumes that this is true in raw encoding, but well, hopefully there are no servers running in japanese on protocol level...
  int idx = msg.indexOf(" :");
  if(idx >= 0) {
    if(msg.length() > idx + 2)
      trailing = msg.mid(idx + 2);
    msg = msg.left(idx);
  }
  // OK, now it is safe to split...
  QList<QByteArray> params = msg.split(' ');

  // This could still contain empty elements due to (faulty?) ircds sending multiple spaces in a row
  // Also, QByteArray is not nearly as convenient to work with as QString for such things :)
  QList<QByteArray>::iterator iter = params.begin();
  while(iter != params.end()) {
    if(iter->isEmpty())
      iter = params.erase(iter);
    else
      ++iter;
  }

  if(!trailing.isEmpty())
    params << trailing;
  if(params.count() < 1) {
    qWarning() << "Received invalid string from server!";
    return;
  }

  QString foo = net->serverDecode(params.takeFirst());

  // a colon as the first chars indicates the existence of a prefix
  if(foo[0] == ':') {
    foo.remove(0, 1);
    prefix = foo;
    if(params.count() < 1) {
      qWarning() << "Received invalid string from server!";
      return;
    }
    foo = net->serverDecode(params.takeFirst());
  }

  // next string without a whitespace is the command
  cmd = foo.trimmed();

  QList<Event *> events;
  EventManager::EventType type = EventManager::Invalid;

  // numeric replies have the target as first param (RFC 2812 - 2.4). this is usually our own nick. Remove this!
  uint num = cmd.toUInt();
  if(num > 0) {
    if(params.count() == 0) {
      qWarning() << "Message received from server violates RFC and is ignored!" << msg;
      return;
    }
    target = net->serverDecode(params.takeFirst());
    type = EventManager::IrcEventNumeric;
  } else {
    QString typeName = QLatin1String("IrcEvent") + cmd.at(0).toUpper() + cmd.mid(1).toLower();
    type = eventManager()->eventTypeByName(typeName);
    if(type == EventManager::Invalid) {
      type = eventManager()->eventTypeByName("IrcEventUnknown");
      Q_ASSERT(type != EventManager::Invalid);
    }
    target = QString();
  }

  // Almost always, all params are server-encoded. There's a few exceptions, let's catch them here!
  // Possibly not the best option, we might want something more generic? Maybe yet another layer of
  // unencoded events with event handlers for the exceptions...
  // Also, PRIVMSG and NOTICE need some special handling, we put this in here as well, so we get out
  // nice pre-parsed events that the CTCP handler can consume.

  QStringList decParams;
  bool defaultHandling = true; // whether to automatically copy the remaining params and send the event

  switch(type) {

  case EventManager::IrcEventPrivmsg:
    defaultHandling = false; // this might create a list of events

    if(checkParamCount(cmd, params, 1)) {
      QString senderNick = nickFromMask(prefix);
      QByteArray msg = params.count() < 2 ? QByteArray() : params.at(1);

      QStringList targets = net->serverDecode(params.at(0)).split(',', QString::SkipEmptyParts);
      QStringList::const_iterator targetIter;
      for(targetIter = targets.constBegin(); targetIter != targets.constEnd(); ++targetIter) {
        QString target = net->isChannelName(*targetIter) ? *targetIter : senderNick;

        msg = decrypt(net, target, msg);

        events << new IrcEventRawMessage(EventManager::IrcEventRawPrivmsg, net, msg, prefix, target, e->timestamp());
      }
    }
    break;

  case EventManager::IrcEventNotice:
    defaultHandling = false;

    if(checkParamCount(cmd, params, 2)) {
      QStringList targets = net->serverDecode(params.at(0)).split(',', QString::SkipEmptyParts);
      QStringList::const_iterator targetIter;
      for(targetIter = targets.constBegin(); targetIter != targets.constEnd(); ++targetIter) {
        QString target = *targetIter;

        // special treatment for welcome messages like:
        // :ChanServ!ChanServ@services. NOTICE egst :[#apache] Welcome, this is #apache. Please read the in-channel topic message. This channel is being logged by IRSeekBot. If you have any question please see http://blog.freenode.net/?p=68
        if(!net->isChannelName(target)) {
          QString decMsg = net->serverDecode(params.at(1));
          QRegExp welcomeRegExp("^\\[([^\\]]+)\\] ");
          if(welcomeRegExp.indexIn(decMsg) != -1) {
            QString channelname = welcomeRegExp.cap(1);
            decMsg = decMsg.mid(welcomeRegExp.matchedLength());
            CoreIrcChannel *chan = static_cast<CoreIrcChannel *>(net->ircChannel(channelname)); // we only have CoreIrcChannels in the core, so this cast is safe
            if(chan && !chan->receivedWelcomeMsg()) {
              chan->setReceivedWelcomeMsg();
              events << new MessageEvent(Message::Notice, net, decMsg, prefix, channelname, Message::None, e->timestamp());
              continue;
            }
          }
        }

        if(prefix.isEmpty() || target == "AUTH") {
          target = QString();
        } else {
          if(!target.isEmpty() && net->prefixes().contains(target.at(0)))
            target = target.mid(1);
          if(!net->isChannelName(target))
            target = nickFromMask(prefix);
        }
        events << new IrcEventRawMessage(EventManager::IrcEventRawNotice, net, params[1], prefix, target, e->timestamp());
      }
    }
    break;

    // the following events need only special casing for param decoding
  case EventManager::IrcEventKick:
    if(params.count() >= 3) { // we have a reason
      decParams << net->serverDecode(params.at(0)) << net->serverDecode(params.at(1));
      decParams << net->channelDecode(decParams.first(), params.at(2)); // kick reason
    }
    break;

  case EventManager::IrcEventPart:
    if(params.count() >= 2) {
      QString channel = net->serverDecode(params.at(0));
      decParams << channel;
      decParams << net->userDecode(nickFromMask(prefix), params.at(1));
    }
    break;

  case EventManager::IrcEventQuit:
    if(params.count() >= 1) {
      decParams << net->userDecode(nickFromMask(prefix), params.at(0));
    }
    break;

  case EventManager::IrcEventTopic:
    if(params.count() >= 1) {
      QString channel = net->serverDecode(params.at(0));
      decParams << channel;
      decParams << (params.count() >= 2? net->channelDecode(channel, decrypt(net, channel, params.at(1), true)) : QString());
    }
    break;

  case EventManager::IrcEventNumeric:
    switch(num) {
    case 301:  /* RPL_AWAY */
      if(params.count() >= 2) {
        QString nick = net->serverDecode(params.at(0));
        decParams << nick;
        decParams << net->userDecode(nick, params.at(1));
      }
      break;

    case 332:  /* RPL_TOPIC */
      if(params.count() >= 2) {
        QString channel = net->serverDecode(params.at(0));
        decParams << channel;
        decParams << net->channelDecode(channel, decrypt(net, channel, params.at(1), true));
      }
      break;

    case 333:  /* Topic set by... */
      if(params.count() >= 2) {
        QString channel = net->serverDecode(params.at(0));
        decParams << channel << net->serverDecode(params.at(1));
        decParams << net->channelDecode(channel, params.at(2));
      }
      break;
    }

  default:
    break;
  }

  if(defaultHandling && type != EventManager::Invalid) {
    for(int i = decParams.count(); i < params.count(); i++)
      decParams << net->serverDecode(params.at(i));

    IrcEvent *event;
    if(type == EventManager::IrcEventNumeric)
      event = new IrcEventNumeric(num, net, prefix, target);
    else
      event = new IrcEvent(type, net, prefix);
    event->setParams(decParams);
    event->setTimestamp(e->timestamp());
    events << event;
  }

  foreach(Event *event, events) {
    coreSession()->eventManager()->sendEvent(event);
  }
}