ircresponseparser.cpp
1 //------------------------------------------------------------------------------
2 // ircresponseparser.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2010 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "ircresponseparser.h"
24 
25 #include "irc/constants/ircresponsetype.h"
26 #include "irc/ircctcpparser.h"
27 #include "irc/ircglobal.h"
28 #include "irc/ircmessageclass.h"
29 #include "irc/ircnetworkadapter.h"
30 #include "irc/ircuserinfo.h"
31 #include "log.h"
32 #include "patternlist.h"
33 #include "strings.hpp"
34 #include <cassert>
35 #include <QDateTime>
36 #include <QRegExp>
37 #include <QStringList>
38 
39 DClass<IRCResponseParser>
40 {
41 public:
42  IRCNetworkAdapter *network;
43  QString prefix;
44  QString sender;
45  QString type;
46  QStringList params;
47 };
48 
49 DPointered(IRCResponseParser)
50 
52 {
53  d->network = network;
54 }
55 
56 IRCResponseParser::~IRCResponseParser()
57 {
58 }
59 
60 IRCResponseParser::FlagModes IRCResponseParser::getFlagMode(char c)
61 {
62  switch (c)
63  {
64  case '+':
65  return FlagModeAdd;
66 
67  case '-':
68  return FlagModeRemove;
69 
70  default:
71  return FlagModeError;
72  }
73 }
74 
75 bool IRCResponseParser::isPrefixIgnored() const
76 {
77  return d->network->ignoredUsersPatterns().isExactMatchAny(d->prefix);
78 }
79 
80 QString IRCResponseParser::joinAndTrimColonIfNecessary(const QStringList &strList) const
81 {
82  QString joined = strList.join(" ");
83  return this->trimColonIfNecessary(joined);
84 }
85 
87 {
88  QString formattedMessage = message.trimmed();
89 
90  QRegExp prefixRegExp("(^:\\S+\\s)(.*)");
91  prefixRegExp.indexIn(formattedMessage);
92 
93  QString prefix = prefixRegExp.cap(1);
94  QString remainder = formattedMessage.mid(prefix.length());
95 
96  d->prefix = Strings::triml(prefix, ":").trimmed();
97 
98  // Obtain message sender from the prefix.
99  int indexExclamation = prefix.indexOf('!');
100  if (indexExclamation > 0)
101  d->sender = d->prefix.left(indexExclamation);
102  else
103  d->sender = d->prefix;
104 
105  QStringList msgParameters = remainder.split(" ");
106  if (!msgParameters.isEmpty())
107  {
108  d->type = msgParameters.takeFirst();
109  d->params = msgParameters;
110 
111  return parseMessage();
112  }
113 
114  // Return invalid result.
115  return IRCResponseParseResult();
116 }
117 
118 IRCResponseParseResult IRCResponseParser::parseMessage()
119 {
120  IRCResponseType responseType(d->type);
121  IRCResponseType::MsgType enumType = responseType.type();
122 
123  switch (enumType)
124  {
126  {
127  QString nickname = d->params.takeFirst();
128 
129  emit helloClient(nickname);
130  break;
131  }
132 
134  {
135  d->params.takeFirst(); // Own nick.
136  QString nickname = d->params.takeFirst();
137  QString reason = joinAndTrimColonIfNecessary(d->params);
138  emit printToNetworksCurrentChatBox(tr("User %1 is away: %2").arg(nickname, reason),
139  IRCMessageClass::NetworkAction);
140  break;
141  }
142 
144  {
145  // First param is unnecessary
146  d->params.takeFirst();
147 
148  // Extract user info.
149  QString nickname = d->params.takeFirst();
150  QString user = d->params.takeFirst();
151  QString hostName = d->params.takeFirst();
152  QString realName = joinAndTrimColonIfNecessary(d->params);
153 
154  emit whoIsUser(nickname, user, hostName, realName);
155  break;
156  }
157 
168  {
169  d->params.takeFirst(); // Own nick.
170  emit printToNetworksCurrentChatBox(d->params.join(" "), IRCMessageClass::NetworkAction);
171  break;
172  }
173 
175  {
176  d->params.takeFirst(); // Own nick.
177  QString nick = d->params.takeFirst();
178  int secondsIdle = d->params.takeFirst().toInt();
179  emit userIdleTime(nick, secondsIdle);
180  if (d->params.first().toInt() != 0)
181  {
182  int joinedOn = d->params.takeFirst().toInt();
183  emit userNetworkJoinDateTime(nick, QDateTime::fromTime_t(joinedOn));
184  }
185  break;
186  }
187 
189  {
190  d->params.takeFirst(); // Own nick.
191  QString nick = d->params.takeFirst();
192  QString channels = joinAndTrimColonIfNecessary(d->params);
193  emit printToNetworksCurrentChatBox(tr("%1 is on channels: %2").arg(nick, channels),
194  IRCMessageClass::NetworkAction);
195  break;
196  }
197 
199  {
200  d->params.takeFirst(); // Own nick.
201  QString nick = d->params.takeFirst();
202  QString account = d->params.takeFirst();
203  QString message = joinAndTrimColonIfNecessary(d->params);
204  emit printToNetworksCurrentChatBox(QString("%1 %2 %3").arg(nick, message, account),
205  IRCMessageClass::NetworkAction);
206  break;
207  }
208 
210  {
211  d->params.takeFirst(); // Own nickname.
212  emit iSupportReceived(d->params.join(" "));
213  break;
214  }
215 
217  {
218  d->params.takeFirst(); // Own nickname.
219  QString channel = d->params.takeFirst();
220  QString topic = joinAndTrimColonIfNecessary(d->params);
221  QString msg = tr("Topic: %1").arg(topic);
222  emit printWithClass(msg, channel, IRCMessageClass(IRCMessageClass::ChannelAction));
223  break;
224  }
225 
227  {
228  d->params.takeFirst(); // Own nickname.
229  QString channel = d->params.takeFirst();
230  QString who = d->params.takeFirst();
231  qint64 timestampSeconds = d->params.takeFirst().toLongLong();
232  QDateTime date = QDateTime::fromMSecsSinceEpoch(timestampSeconds * 1000);
233  QString msg = tr("Topic set by %1 on %2.").arg(who, date.toString("yyyy-MM-dd hh:mm:ss"));
234  emit printWithClass(msg, channel, IRCMessageClass(IRCMessageClass::ChannelAction));
235  break;
236  }
237 
239  {
240  d->params.takeFirst(); // Own nickname.
241  QString channel = d->params.takeFirst();
242  QString url = joinAndTrimColonIfNecessary(d->params);
243  emit printWithClass(tr("URL: %1").arg(url), channel, IRCMessageClass::ChannelAction);
244  break;
245  }
246 
248  {
249  d->params.takeFirst(); // Own nickname.
250  QString channel = d->params.takeFirst();
251  QString time = joinAndTrimColonIfNecessary(d->params);
252  emit printWithClass(tr("Created time: %1").arg(time), channel,
253  IRCMessageClass::ChannelAction);
254  break;
255  }
256 
258  {
259  // Namelists.
260 
261  // Attempt to extract the channel name. For some reason
262  // irc.skulltag.net returns a '=' character between the message type
263  // signature and the channel name. RFC 1459 doesn't say anything about
264  // such behavior, at least not in the chapter 6. We should protect
265  // ourselves from such unwelcome surprises.
266  QString channelName = "";
267  while (!IRCGlobal::isChannelName(channelName) && !d->params.isEmpty())
268  {
269  channelName = d->params.takeFirst();
270  }
271 
272  if (channelName.isEmpty())
273  {
274  emit parseError(tr("RPLNamReply: Received names list but no channel name."));
275  return IRCResponseParseResult();
276  }
277 
278  // Remaining values will be user names. Send them all as a strings list.
279  // Remember to remove the ":" character from the first name.
280  if (!d->params.isEmpty())
281  d->params[0] = d->params[0].remove(0, 1);
282 
283  emit namesListReceived(channelName, d->params);
284  break;
285  }
286 
288  {
289  QString channel = d->params[1];
290  emit namesListEndReceived(channel);
291  break;
292  }
293 
297  {
298  // First param is username, drop it.
299  d->params.takeFirst();
300 
301  if (enumType == IRCResponseType::RPLMOTDStart)
302  {
303  // We will print additional separator line if it's a start of
304  // the MOTD.
305  emit print("\n----------------------", QString());
306  }
307 
308  emit print(joinAndTrimColonIfNecessary(d->params), QString());
309 
310  if (enumType == IRCResponseType::RPLEndOfMOTD)
311  {
312  // Again, we will print additional separator line if it's the
313  // end of the MOTD.
314  emit print("----------------------\n", QString());
315  }
316 
317  break;
318  }
319 
320  // Extract correct message and print it.
323  {
324  // Drop the first param.
325  d->params.takeFirst();
326 
327  // Join and print the rest.
328  emit print(joinAndTrimColonIfNecessary(d->params), QString());
329  break;
330  }
331 
332  // Extract correct message and print it.
336  {
337  // Drop the first param.
338  d->params.takeFirst();
339 
340  // Here the first param is always an integer, and colon is located
341  // afterwards.
342  QString number = d->params.takeFirst();
343  emit print(number + " " + joinAndTrimColonIfNecessary(d->params), QString());
344  break;
345  }
346 
348  {
349  // Drop the first param.
350  d->params.takeFirst();
351 
352  // This is the real nickname.
353  QString nickname = d->params.takeFirst();
354 
355  emit noSuchNickname(nickname);
356  break;
357  }
358 
360  {
361  // Own nickname.
362  d->params.takeFirst();
363  QString badNick = d->params.takeFirst();
364  QString msg = tr("Erroneous nickname: %1").arg(badNick);
365  if (d->params.join(" ").compare(":Erroneous nickname", Qt::CaseInsensitive) != 0)
366  msg += tr(" (%1)").arg(joinAndTrimColonIfNecessary(d->params));
367  emit printToNetworksCurrentChatBox(msg, IRCMessageClass::Error);
368  break;
369  }
370 
372  {
373  // Drop the first param.
374  d->params.takeFirst();
375 
376  QString nickname = d->params.takeFirst();
377 
378  emit nicknameInUse(nickname);
379  break;
380  }
381 
382  case IRCResponseType::ERRChannelIsFull:
383  case IRCResponseType::ERRInviteOnlyChan:
384  case IRCResponseType::ERRBannedFromChan:
385  case IRCResponseType::ERRBadChannelKey:
386  case IRCResponseType::ERRBadChannelMask:
387  case IRCResponseType::ERRNoChanModes:
390  {
391  d->params.takeFirst(); // User
392  QString channel = d->params.takeFirst();
393  QString reason = joinAndTrimColonIfNecessary(d->params);
394  switch (enumType)
395  {
396  case IRCResponseType::ERRChannelIsFull:
397  case IRCResponseType::ERRInviteOnlyChan:
398  case IRCResponseType::ERRBannedFromChan:
399  case IRCResponseType::ERRBadChannelKey:
400  emit printToNetworksCurrentChatBox(tr("%1: %2").arg(channel, reason),
401  IRCMessageClass::Error);
402  break;
403  default:
404  emit printWithClass(reason, channel, IRCMessageClass::ChannelAction);
405  break;
406  }
407 
408  break;
409  }
410 
411  case IRCResponseType::Join:
412  {
413  QString channel = d->params[0];
414  trimColonIfNecessary(channel);
415 
416  emit userJoinsChannel(channel, d->sender, d->prefix);
417  break;
418  }
419 
420  case IRCResponseType::Kick:
421  {
422  QString channel = d->params.takeFirst();
423  QString whoIsKicked = d->params.takeFirst();
424 
425  QString reason = joinAndTrimColonIfNecessary(d->params);
426 
427  emit kick(channel, d->sender, whoIsKicked, reason);
428  break;
429  }
430 
431  case IRCResponseType::Kill:
432  {
433  QString victim = d->params.takeFirst();
434  QString comment = joinAndTrimColonIfNecessary(d->params);
435  emit kill(victim, comment);
436  break;
437  }
438 
439  case IRCResponseType::Mode:
440  {
441  QString channel = d->params.takeFirst();
442  QString flagsString = d->params.takeFirst();
443 
444  // If there are no more params left on the list then this modes
445  // are for the channel itself. Otherwise they are for the users.
446  if (!d->params.isEmpty())
447  {
448  emit modeInfo(channel, d->sender, flagsString + " " + d->params.join(" "));
449  parseUserModeMessage(channel, flagsString, d->params);
450  }
451 
452  break;
453  }
454 
455  case IRCResponseType::Nick:
456  {
457  QString oldNickname = d->sender;
458  QString newNickname = d->params[0];
459  trimColonIfNecessary(newNickname);
460 
461  emit userChangesNickname(oldNickname, newNickname);
462  break;
463  }
464 
465  case IRCResponseType::Part:
466  {
467  QString farewellMessage = QString();
468  QString channel = d->params[0];
469 
470  if (d->params.size() > 1)
471  {
472  d->params.pop_front();
473 
474  farewellMessage = joinAndTrimColonIfNecessary(d->params);
475  }
476 
477  emit userPartsChannel(channel, d->sender, farewellMessage);
478  break;
479  }
480 
481  case IRCResponseType::Ping:
482  {
483  QString pongToWhom = d->params[0];
484 
485  emit sendPongMessage(pongToWhom);
486  break;
487  }
488 
489  case IRCResponseType::PrivMsg:
490  case IRCResponseType::Notice:
491  parsePrivMsgOrNotice();
492  break;
493 
494  case IRCResponseType::Quit:
495  {
496  QString farewellMessage = QString();
497  farewellMessage = joinAndTrimColonIfNecessary(d->params);
498 
499  emit userQuitsNetwork(d->sender, farewellMessage);
500  break;
501  }
502 
503  case IRCResponseType::Topic:
504  {
505  QString channel = d->params.takeFirst();
506  QString topic = joinAndTrimColonIfNecessary(d->params);
507  QString msg = tr("New topic set by user %1:\n%2").arg(d->sender, topic);
508  emit printWithClass(msg, channel, IRCMessageClass(IRCMessageClass::ChannelAction));
509  break;
510  }
511 
513  {
514  // Messages below 100 are some generic server responses to connect
515  // event.
516  if (responseType.numericType() > 1)
517  {
518  emit print(joinAndTrimColonIfNecessary(d->params), "");
519  return IRCResponseParseResult(responseType, true);
520  }
521 
522  return IRCResponseParseResult(responseType, false);
523  }
524 
525  default:
526  emit parseError(tr(
527  "IRCResponseParser: Type '%1' was recognized but there has been no parse code implemented for it.\
528 (yep, it's a bug in the application!)"
529  ).arg(d->type));
530  return IRCResponseParseResult(responseType, true);
531  }
532 
533  return IRCResponseParseResult(responseType, true);
534 }
535 
536 void IRCResponseParser::parsePrivMsgOrNotice()
537 {
538  if (isPrefixIgnored())
539  return;
540  QString recipient = d->params.takeFirst();
541  if (!IRCGlobal::isChannelName(recipient))
542  {
543  // If recipient name is not the channel the
544  // "recipient" QString will point to this client's user.
545  // In order to get a proper recipient we need to use the
546  // "sender" QString instead.
547  recipient = d->sender;
548  }
549 
550  // Join the list to form message contents.
551  QString content = joinAndTrimColonIfNecessary(d->params);
552 
553  IRCResponseType responseType(d->type);
554  IRCCtcpParser::MessageType ctcpMsgType = (responseType == IRCResponseType::Notice) ?
556  IRCCtcpParser ctcp(d->network, d->sender, recipient, content, ctcpMsgType);
557  if (ctcp.parse())
558  {
559  switch (ctcp.echo())
560  {
561  case IRCCtcpParser::PrintAsNormalMessage:
562  emit privMsgLiteralReceived(recipient, ctcp.printable(), IRCMessageClass::Ctcp);
563  break;
564  case IRCCtcpParser::DisplayInServerTab:
565  emit printWithClass(ctcp.printable(), QString(), IRCMessageClass::Ctcp);
566  break;
567  case IRCCtcpParser::DisplayThroughGlobalMessage:
568  emit printToNetworksCurrentChatBox(ctcp.printable(), IRCMessageClass::Ctcp);
569  break;
570  case IRCCtcpParser::DontShow:
571  break;
572  default:
573  gLog << QString("Unhandled CTCP echo type: %1").arg(ctcp.echo());
574  assert(false && "Unhandled CTCP echo type");
575  break;
576  }
577  if (!ctcp.reply().isEmpty() && responseType.type() != IRCResponseType::Notice)
578  d->network->sendMessage(QString("/NOTICE %1 %2%3%2").arg(d->sender, QChar(0x1), ctcp.reply()));
579  }
580  else
581  {
582  if (responseType == IRCResponseType::PrivMsg)
583  emit privMsgReceived(recipient, d->sender, content);
584  else if (responseType == IRCResponseType::Notice)
585  emit print(tr("[%1]: %2").arg(d->sender, content), recipient);
586  else
587  emit parseError(tr("Type '%1' was incorrectly parsed in PrivMsg block.").arg(d->type));
588  }
589 }
590 
591 void IRCResponseParser::parseUserModeMessage(const QString &channel, QString flagsString, QStringList &nicknames)
592 {
593  // For each flag character there should be one nickname on the list.
594  // If there are less nicknames than characters we will simply abort.
595  // Of course add/subtract characters are not counted here.
596 
597  // The first character should always define the flagMode.
598  FlagModes flagMode = getFlagMode(flagsString[0].toLatin1());
599 
600  if (flagMode == FlagModeError)
601  {
602  emit parseError(tr("MODE flags string from IRC server are incorrect: \"%1\". "
603  "Information for channel \"%2\" might not be correct anymore.")
604  .arg(flagsString, channel));
605  return;
606  }
607 
608  for (int i = 1; i < flagsString.size(); ++i)
609  {
610  char flagChar = flagsString[i].toLatin1();
611 
612  FlagModes tmpFlagMode = getFlagMode(flagChar);
613  if (tmpFlagMode == FlagModeError)
614  {
615  if (nicknames.empty())
616  return;
617 
618  QList<char> addedFlags;
619  QList<char> removedFlags;
620 
621  QString name = nicknames[0];
622 
623  switch (flagMode)
624  {
625  case FlagModeAdd:
626  addedFlags << flagChar;
627  break;
628 
629  case FlagModeRemove:
630  removedFlags << flagChar;
631  break;
632 
633  default:
634  emit parseError(tr("IRCResponseParser::parseUserModeMessage(): "
635  "wrong FlagMode. Information for channel \"%2\" might not be correct anymore."));
636  return;
637  }
638 
639  emit userModeChanged(channel, name, addedFlags, removedFlags);
640  // Drop a name from the list and continue.
641  nicknames.pop_front();
642  }
643  else
644  {
645  flagMode = tmpFlagMode;
646  continue;
647  }
648  }
649 }
650 
651 QString &IRCResponseParser::trimColonIfNecessary(QString &str) const
652 {
653  if (!str.isEmpty() && str[0] == ':')
654  str.remove(0, 1);
655 
656  return str;
657 }