ircresponseparser.cpp
1 //------------------------------------------------------------------------------
2 // ircresponseparser.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the 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 <QDateTime>
26 #include <QRegExp>
27 #include <QStringList>
28 #include <cassert>
29 #include "irc/constants/ircresponsetype.h"
30 #include "irc/ircctcpparser.h"
31 #include "irc/ircglobal.h"
32 #include "irc/ircmessageclass.h"
33 #include "irc/ircnetworkadapter.h"
34 #include "irc/ircuserinfo.h"
35 #include "log.h"
36 #include "patternlist.h"
37 #include "strings.h"
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  {
102  d->sender = d->prefix.left(indexExclamation);
103  }
104  else
105  {
106  d->sender = d->prefix;
107  }
108 
109  QStringList msgParameters = remainder.split(" ");
110  if (!msgParameters.isEmpty())
111  {
112  d->type = msgParameters.takeFirst();
113  d->params = msgParameters;
114 
115  return parseMessage();
116  }
117 
118  // Return invalid result.
119  return IRCResponseParseResult();
120 }
121 
122 IRCResponseParseResult IRCResponseParser::parseMessage()
123 {
124  IRCResponseType responseType(d->type);
125  IRCResponseType::MsgType enumType = responseType.type();
126 
127  switch (enumType)
128  {
130  {
131  QString nickname = d->params.takeFirst();
132 
133  emit helloClient(nickname);
134  break;
135  }
136 
138  {
139  d->params.takeFirst(); // Own nick.
140  QString nickname = d->params.takeFirst();
141  QString reason = joinAndTrimColonIfNecessary(d->params);
142  emit printToNetworksCurrentChatBox(tr("User %1 is away: %2").arg(nickname, reason),
143  IRCMessageClass::NetworkAction);
144  break;
145  }
146 
148  {
149  // First param is unnecessary
150  d->params.takeFirst();
151 
152  // Extract user info.
153  QString nickname = d->params.takeFirst();
154  QString user = d->params.takeFirst();
155  QString hostName = d->params.takeFirst();
156  QString realName = joinAndTrimColonIfNecessary(d->params);
157 
158  emit whoIsUser(nickname, user, hostName, realName);
159  break;
160  }
161 
172  {
173  d->params.takeFirst(); // Own nick.
174  emit printToNetworksCurrentChatBox(d->params.join(" "), IRCMessageClass::NetworkAction);
175  break;
176  }
177 
179  {
180  d->params.takeFirst(); // Own nick.
181  QString nick = d->params.takeFirst();
182  int secondsIdle = d->params.takeFirst().toInt();
183  emit userIdleTime(nick, secondsIdle);
184  if (d->params.first().toInt() != 0)
185  {
186  int joinedOn = d->params.takeFirst().toInt();
187  emit userNetworkJoinDateTime(nick, QDateTime::fromTime_t(joinedOn));
188  }
189  break;
190  }
191 
193  {
194  d->params.takeFirst(); // Own nick.
195  QString nick = d->params.takeFirst();
196  QString channels = joinAndTrimColonIfNecessary(d->params);
197  emit printToNetworksCurrentChatBox(tr("%1 is on channels: %2").arg(nick, channels),
198  IRCMessageClass::NetworkAction);
199  break;
200  }
201 
203  {
204  d->params.takeFirst(); // Own nick.
205  QString nick = d->params.takeFirst();
206  QString account = d->params.takeFirst();
207  QString message = joinAndTrimColonIfNecessary(d->params);
208  emit printToNetworksCurrentChatBox(QString("%1 %2 %3").arg(nick, message, account),
209  IRCMessageClass::NetworkAction);
210  break;
211  }
212 
214  {
215  d->params.takeFirst(); // Own nickname.
216  emit iSupportReceived(d->params.join(" "));
217  break;
218  }
219 
221  {
222  d->params.takeFirst(); // Own nickname.
223  QString channel = d->params.takeFirst();
224  QString topic = joinAndTrimColonIfNecessary(d->params);
225  QString msg = tr("Topic: %1").arg(topic);
226  emit printWithClass(msg, channel, IRCMessageClass(IRCMessageClass::ChannelAction));
227  break;
228  }
229 
231  {
232  d->params.takeFirst(); // Own nickname.
233  QString channel = d->params.takeFirst();
234  QString who = d->params.takeFirst();
235  qint64 timestampSeconds = d->params.takeFirst().toLongLong();
236  QDateTime date = QDateTime::fromMSecsSinceEpoch(timestampSeconds * 1000);
237  QString msg = tr("Topic set by %1 on %2.").arg(who, date.toString("yyyy-MM-dd hh:mm:ss"));
238  emit printWithClass(msg, channel, IRCMessageClass(IRCMessageClass::ChannelAction));
239  break;
240  }
241 
243  {
244  d->params.takeFirst(); // Own nickname.
245  QString channel = d->params.takeFirst();
246  QString url = joinAndTrimColonIfNecessary(d->params);
247  emit printWithClass(tr("URL: %1").arg(url), channel, IRCMessageClass::ChannelAction);
248  break;
249  }
250 
252  {
253  d->params.takeFirst(); // Own nickname.
254  QString channel = d->params.takeFirst();
255  QString time = joinAndTrimColonIfNecessary(d->params);
256  emit printWithClass(tr("Created time: %1").arg(time), channel,
257  IRCMessageClass::ChannelAction);
258  break;
259  }
260 
262  {
263  // Namelists.
264 
265  // Attempt to extract the channel name. For some reason
266  // irc.skulltag.net returns a '=' character between the message type
267  // signature and the channel name. RFC 1459 doesn't say anything about
268  // such behavior, at least not in the chapter 6. We should protect
269  // ourselves from such unwelcome surprises.
270  QString channelName = "";
271  while (!IRCGlobal::isChannelName(channelName) && !d->params.isEmpty())
272  {
273  channelName = d->params.takeFirst();
274  }
275 
276  if (channelName.isEmpty())
277  {
278  emit parseError(tr("RPLNamReply: Received names list but no channel name."));
279  return IRCResponseParseResult();
280  }
281 
282  // Remaining values will be user names. Send them all as a strings list.
283  // Remember to remove the ":" character from the first name.
284  if (!d->params.isEmpty())
285  {
286  d->params[0] = d->params[0].remove(0, 1);
287  }
288 
289  emit namesListReceived(channelName, d->params);
290  break;
291  }
292 
294  {
295  QString channel = d->params[1];
296  emit namesListEndReceived(channel);
297  break;
298  }
299 
303  {
304  // First param is username, drop it.
305  d->params.takeFirst();
306 
307  if (enumType == IRCResponseType::RPLMOTDStart)
308  {
309  // We will print additional separator line if it's a start of
310  // the MOTD.
311  emit print("\n----------------------", QString());
312  }
313 
314  emit print(joinAndTrimColonIfNecessary(d->params), QString());
315 
316  if (enumType == IRCResponseType::RPLEndOfMOTD)
317  {
318  // Again, we will print additional separator line if it's the
319  // end of the MOTD.
320  emit print("----------------------\n", QString());
321  }
322 
323  break;
324  }
325 
326  // Extract correct message and print it.
329  {
330  // Drop the first param.
331  d->params.takeFirst();
332 
333  // Join and print the rest.
334  emit print(joinAndTrimColonIfNecessary(d->params), QString());
335  break;
336  }
337 
338  // Extract correct message and print it.
342  {
343  // Drop the first param.
344  d->params.takeFirst();
345 
346  // Here the first param is always an integer, and colon is located
347  // afterwards.
348  QString number = d->params.takeFirst();
349  emit print(number + " " + joinAndTrimColonIfNecessary(d->params), QString());
350  break;
351  }
352 
354  {
355  // Drop the first param.
356  d->params.takeFirst();
357 
358  // This is the real nickname.
359  QString nickname = d->params.takeFirst();
360 
361  emit noSuchNickname(nickname);
362  break;
363  }
364 
366  {
367  // Own nickname.
368  d->params.takeFirst();
369  QString badNick = d->params.takeFirst();
370  QString msg = tr("Erroneous nickname: %1").arg(badNick);
371  if (d->params.join(" ").compare(":Erroneous nickname", Qt::CaseInsensitive) != 0)
372  {
373  msg += tr(" (%1)").arg(joinAndTrimColonIfNecessary(d->params));
374  }
375  emit printToNetworksCurrentChatBox(msg, IRCMessageClass::Error);
376  break;
377  }
378 
380  {
381  // Drop the first param.
382  d->params.takeFirst();
383 
384  QString nickname = d->params.takeFirst();
385 
386  emit nicknameInUse(nickname);
387  break;
388  }
389 
390  case IRCResponseType::ERRChannelIsFull:
391  case IRCResponseType::ERRInviteOnlyChan:
392  case IRCResponseType::ERRBannedFromChan:
393  case IRCResponseType::ERRBadChannelKey:
394  case IRCResponseType::ERRBadChannelMask:
395  case IRCResponseType::ERRNoChanModes:
398  {
399  d->params.takeFirst(); // User
400  QString channel = d->params.takeFirst();
401  QString reason = joinAndTrimColonIfNecessary(d->params);
402  switch (enumType)
403  {
404  case IRCResponseType::ERRChannelIsFull:
405  case IRCResponseType::ERRInviteOnlyChan:
406  case IRCResponseType::ERRBannedFromChan:
407  case IRCResponseType::ERRBadChannelKey:
408  emit printToNetworksCurrentChatBox(tr("%1: %2").arg(channel, reason),
409  IRCMessageClass::Error);
410  break;
411  default:
412  emit printWithClass(reason, channel, IRCMessageClass::ChannelAction);
413  break;
414  }
415 
416  break;
417  }
418 
419  case IRCResponseType::Join:
420  {
421  QString channel = d->params[0];
422  trimColonIfNecessary(channel);
423 
424  emit userJoinsChannel(channel, d->sender, d->prefix);
425  break;
426  }
427 
428  case IRCResponseType::Kick:
429  {
430  QString channel = d->params.takeFirst();
431  QString whoIsKicked = d->params.takeFirst();
432 
433  QString reason = joinAndTrimColonIfNecessary(d->params);
434 
435  emit kick(channel, d->sender, whoIsKicked, reason);
436  break;
437  }
438 
439  case IRCResponseType::Kill:
440  {
441  QString victim = d->params.takeFirst();
442  QString comment = joinAndTrimColonIfNecessary(d->params);
443  emit kill(victim, comment);
444  break;
445  }
446 
447  case IRCResponseType::Mode:
448  {
449  QString channel = d->params.takeFirst();
450  QString flagsString = d->params.takeFirst();
451 
452  // If there are no more params left on the list then this modes
453  // are for the channel itself. Otherwise they are for the users.
454  if (!d->params.isEmpty())
455  {
456  emit modeInfo(channel, d->sender, flagsString + " " + d->params.join(" "));
457  parseUserModeMessage(channel, flagsString, d->params);
458  }
459 
460  break;
461  }
462 
463  case IRCResponseType::Nick:
464  {
465  QString oldNickname = d->sender;
466  QString newNickname = d->params[0];
467  trimColonIfNecessary(newNickname);
468 
469  emit userChangesNickname(oldNickname, newNickname);
470  break;
471  }
472 
473  case IRCResponseType::Part:
474  {
475  QString farewellMessage = QString();
476  QString channel = d->params[0];
477 
478  if (d->params.size() > 1)
479  {
480  d->params.pop_front();
481 
482  farewellMessage = joinAndTrimColonIfNecessary(d->params);
483  }
484 
485  emit userPartsChannel(channel, d->sender, farewellMessage);
486  break;
487  }
488 
489  case IRCResponseType::Ping:
490  {
491  QString pongToWhom = d->params[0];
492 
493  emit sendPongMessage(pongToWhom);
494  break;
495  }
496 
497  case IRCResponseType::PrivMsg:
498  case IRCResponseType::Notice:
499  parsePrivMsgOrNotice();
500  break;
501 
502  case IRCResponseType::Quit:
503  {
504  QString farewellMessage = QString();
505  farewellMessage = joinAndTrimColonIfNecessary(d->params);
506 
507  emit userQuitsNetwork(d->sender, farewellMessage);
508  break;
509  }
510 
511  case IRCResponseType::Topic:
512  {
513  QString channel = d->params.takeFirst();
514  QString topic = joinAndTrimColonIfNecessary(d->params);
515  QString msg = tr("New topic set by user %1:\n%2").arg(d->sender, topic);
516  emit printWithClass(msg, channel, IRCMessageClass(IRCMessageClass::ChannelAction));
517  break;
518  }
519 
521  {
522  // Messages below 100 are some generic server responses to connect
523  // event.
524  if (responseType.numericType() > 1)
525  {
526  emit print(joinAndTrimColonIfNecessary(d->params), "");
527  return IRCResponseParseResult(responseType, true);
528  }
529 
530  return IRCResponseParseResult(responseType, false);
531  }
532 
533  default:
534  emit parseError(tr(
535  "IRCResponseParser: Type '%1' was recognized but there has been no parse code implemented for it.\
536 (yep, it's a bug in the application!)"
537  ).arg(d->type));
538  return IRCResponseParseResult(responseType, true);
539 
540 
541  }
542 
543  return IRCResponseParseResult(responseType, true);
544 }
545 
546 void IRCResponseParser::parsePrivMsgOrNotice()
547 {
548  if (isPrefixIgnored())
549  {
550  return;
551  }
552  QString recipient = d->params.takeFirst();
553  if (!IRCGlobal::isChannelName(recipient))
554  {
555  // If recipient name is not the channel the
556  // "recipient" QString will point to this client's user.
557  // In order to get a proper recipient we need to use the
558  // "sender" QString instead.
559  recipient = d->sender;
560  }
561 
562  // Join the list to form message contents.
563  QString content = joinAndTrimColonIfNecessary(d->params);
564 
565  IRCResponseType responseType(d->type);
566  IRCCtcpParser::MessageType ctcpMsgType = (responseType == IRCResponseType::Notice) ?
568  IRCCtcpParser ctcp(d->network, d->sender, recipient, content, ctcpMsgType);
569  if (ctcp.parse())
570  {
571  switch (ctcp.echo())
572  {
573  case IRCCtcpParser::PrintAsNormalMessage:
574  emit privMsgLiteralReceived(recipient, ctcp.printable(), IRCMessageClass::Ctcp);
575  break;
576  case IRCCtcpParser::DisplayInServerTab:
577  emit printWithClass(ctcp.printable(), QString(), IRCMessageClass::Ctcp);
578  break;
579  case IRCCtcpParser::DisplayThroughGlobalMessage:
580  emit printToNetworksCurrentChatBox(ctcp.printable(), IRCMessageClass::Ctcp);
581  break;
582  case IRCCtcpParser::DontShow:
583  break;
584  default:
585  gLog << QString("Unhandled CTCP echo type: %1").arg(ctcp.echo());
586  assert(false && "Unhandled CTCP echo type");
587  break;
588  }
589  if (!ctcp.reply().isEmpty() && responseType.type() != IRCResponseType::Notice)
590  {
591  d->network->sendMessage(QString("/NOTICE %1 %2%3%2").arg(d->sender, QChar(0x1), ctcp.reply()));
592  }
593  }
594  else
595  {
596  if (responseType == IRCResponseType::PrivMsg)
597  {
598  emit privMsgReceived(recipient, d->sender, content);
599  }
600  else if (responseType == IRCResponseType::Notice)
601  {
602  emit print(tr("[%1]: %2").arg(d->sender, content), recipient);
603  }
604  else
605  {
606  emit parseError(tr("Type '%1' was incorrectly parsed in PrivMsg block.").arg(d->type));
607  }
608  }
609 }
610 
611 void IRCResponseParser::parseUserModeMessage(const QString& channel, QString flagsString, QStringList& nicknames)
612 {
613  // For each flag character there should be one nickname on the list.
614  // If there are less nicknames than characters we will simply abort.
615  // Of course add/subtract characters are not counted here.
616 
617  // The first character should always define the flagMode.
618  FlagModes flagMode = getFlagMode(flagsString[0].toLatin1());
619 
620  if (flagMode == FlagModeError)
621  {
622  emit parseError(tr("MODE flags string from IRC server are incorrect: \"%1\". "
623  "Information for channel \"%2\" might not be correct anymore.")
624  .arg(flagsString, channel));
625  return;
626  }
627 
628  for (int i = 1; i < flagsString.size(); ++i)
629  {
630  char flagChar = flagsString[i].toLatin1();
631 
632  FlagModes tmpFlagMode = getFlagMode(flagChar);
633  if (tmpFlagMode == FlagModeError)
634  {
635  if (nicknames.empty())
636  {
637  return;
638  }
639 
640  QList<char> addedFlags;
641  QList<char> removedFlags;
642 
643  QString name = nicknames[0];
644 
645  switch (flagMode)
646  {
647  case FlagModeAdd:
648  addedFlags << flagChar;
649  break;
650 
651  case FlagModeRemove:
652  removedFlags << flagChar;
653  break;
654 
655  default:
656  emit parseError(tr("IRCResponseParser::parseUserModeMessage(): "
657  "wrong FlagMode. Information for channel \"%2\" might not be correct anymore."));
658  return;
659  }
660 
661  emit userModeChanged(channel, name, addedFlags, removedFlags);
662  // Drop a name from the list and continue.
663  nicknames.pop_front();
664  }
665  else
666  {
667  flagMode = tmpFlagMode;
668  continue;
669  }
670  }
671 }
672 
673 QString& IRCResponseParser::trimColonIfNecessary(QString& str) const
674 {
675  if (!str.isEmpty() && str[0] == ':')
676  {
677  str.remove(0, 1);
678  }
679 
680  return str;
681 }
Interprets communication between the client and the IRC server.
void print(const QString &printWhat, const QString &printWhere)
Tells the network adapter to print a message.
375 - start of the message of the day
void privMsgLiteralReceived(const QString &recipient, const QString &content, const IRCMessageClass &msgClass)
Create chat window if necessary and display message &#39;as is&#39; without further string gluing...
372 - message of the day
An answer is being sent through NOTICE.
Definition: ircctcpparser.h:33
void modeInfo(const QString &channel, const QString &whoSetThis, const QString &modeParams)
Carries info about MODE for display.
254 - how many channels,
IRCResponseParseResult parse(const QString &message)
Parses the message received from the network.
253 - how many unknown connections
366 - end of names list
void whoIsUser(const QString &nickname, const QString &user, const QString &hostName, const QString &realName)
Emitted with response 311 RPL_WHOISUSER.
251 - how many users on how many servers
320 - it&#39;s inconclusive what this code means.
Defines types of IRC network response message.
void helloClient(const QString &nickname)
Emitted when user successfuly joins the network.
A question is being asked through PRIVMSG.
Definition: ircctcpparser.h:29
353 - names list for a channel
376 - end of the message of the day
001 - sent when client connects.
005 - all sorts of server flags.
Result info generated by the IRCResponseParser.
MsgType
Represents types defined by RFC 1459.
307 - no idea what this is, but we&#39;ll treat it the same way we treat RPLWhoIsSpecial.
255 - how many clients on how many servers,
int numericType() const
If message type can be represented as number, this will contain its value.
Type unknown to this IRC client.
void printWithClass(const QString &printWhat, const QString &printWhere, const IRCMessageClass &msgClass)
Same as print(), but allows to specify message class.