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 
171  {
172  d->params.takeFirst(); // Own nick.
173  emit printToNetworksCurrentChatBox(d->params.join(" "), IRCMessageClass::NetworkAction);
174  break;
175  }
176 
178  {
179  d->params.takeFirst(); // Own nick.
180  QString nick = d->params.takeFirst();
181  int secondsIdle = d->params.takeFirst().toInt();
182  emit userIdleTime(nick, secondsIdle);
183  if (d->params.first().toInt() != 0)
184  {
185  int joinedOn = d->params.takeFirst().toInt();
186  emit userNetworkJoinDateTime(nick, QDateTime::fromTime_t(joinedOn));
187  }
188  break;
189  }
190 
192  {
193  d->params.takeFirst(); // Own nick.
194  QString nick = d->params.takeFirst();
195  QString channels = joinAndTrimColonIfNecessary(d->params);
196  emit printToNetworksCurrentChatBox(tr("%1 is on channels: %2").arg(nick, channels),
197  IRCMessageClass::NetworkAction);
198  break;
199  }
200 
202  {
203  d->params.takeFirst(); // Own nick.
204  QString nick = d->params.takeFirst();
205  QString account = d->params.takeFirst();
206  QString message = joinAndTrimColonIfNecessary(d->params);
207  emit printToNetworksCurrentChatBox(QString("%1 %2 %3").arg(nick, message, account),
208  IRCMessageClass::NetworkAction);
209  break;
210  }
211 
213  {
214  d->params.takeFirst(); // Own nickname.
215  emit iSupportReceived(d->params.join(" "));
216  break;
217  }
218 
220  {
221  d->params.takeFirst(); // Own nickname.
222  QString channel = d->params.takeFirst();
223  QString topic = joinAndTrimColonIfNecessary(d->params);
224  QString msg = tr("Topic: %1").arg(topic);
225  emit printWithClass(msg, channel, IRCMessageClass(IRCMessageClass::ChannelAction));
226  break;
227  }
228 
230  {
231  d->params.takeFirst(); // Own nickname.
232  QString channel = d->params.takeFirst();
233  QString who = d->params.takeFirst();
234  qint64 timestampSeconds = d->params.takeFirst().toLongLong();
235  QDateTime date = QDateTime::fromMSecsSinceEpoch(timestampSeconds * 1000);
236  QString msg = tr("Topic set by %1 on %2.").arg(who, date.toString("yyyy-MM-dd hh:mm:ss"));
237  emit printWithClass(msg, channel, IRCMessageClass(IRCMessageClass::ChannelAction));
238  break;
239  }
240 
242  {
243  // Namelists.
244 
245  // Attempt to extract the channel name. For some reason
246  // irc.skulltag.net returns a '=' character between the message type
247  // signature and the channel name. RFC 1459 doesn't say anything about
248  // such behavior, at least not in the chapter 6. We should protect
249  // ourselves from such unwelcome surprises.
250  QString channelName = "";
251  while (!IRCGlobal::isChannelName(channelName) && !d->params.isEmpty())
252  {
253  channelName = d->params.takeFirst();
254  }
255 
256  if (channelName.isEmpty())
257  {
258  emit parseError(tr("RPLNamReply: Received names list but no channel name."));
259  return IRCResponseParseResult();
260  }
261 
262  // Remaining values will be user names. Send them all as a strings list.
263  // Remember to remove the ":" character from the first name.
264  if (!d->params.isEmpty())
265  {
266  d->params[0] = d->params[0].remove(0, 1);
267  }
268 
269  emit namesListReceived(channelName, d->params);
270  break;
271  }
272 
274  {
275  QString channel = d->params[1];
276  emit namesListEndReceived(channel);
277  break;
278  }
279 
283  {
284  // First param is username, drop it.
285  d->params.takeFirst();
286 
287  if (enumType == IRCResponseType::RPLMOTDStart)
288  {
289  // We will print additional separator line if it's a start of
290  // the MOTD.
291  emit print("\n----------------------", QString());
292  }
293 
294  emit print(joinAndTrimColonIfNecessary(d->params), QString());
295 
296  if (enumType == IRCResponseType::RPLEndOfMOTD)
297  {
298  // Again, we will print additional separator line if it's the
299  // end of the MOTD.
300  emit print("----------------------\n", QString());
301  }
302 
303  break;
304  }
305 
306  // Extract correct message and print it.
309  {
310  // Drop the first param.
311  d->params.takeFirst();
312 
313  // Join and print the rest.
314  emit print(joinAndTrimColonIfNecessary(d->params), QString());
315  break;
316  }
317 
318  // Extract correct message and print it.
322  {
323  // Drop the first param.
324  d->params.takeFirst();
325 
326  // Here the first param is always an integer, and colon is located
327  // afterwards.
328  QString number = d->params.takeFirst();
329  emit print(number + " " + joinAndTrimColonIfNecessary(d->params), QString());
330  break;
331  }
332 
334  {
335  // Drop the first param.
336  d->params.takeFirst();
337 
338  // This is the real nickname.
339  QString nickname = d->params.takeFirst();
340 
341  emit noSuchNickname(nickname);
342  break;
343  }
344 
346  {
347  // Own nickname.
348  d->params.takeFirst();
349  QString badNick = d->params.takeFirst();
350  QString msg = tr("Erroneous nickname: %1").arg(badNick);
351  if (d->params.join(" ").compare(":Erroneous nickname", Qt::CaseInsensitive) != 0)
352  {
353  msg += tr(" (%1)").arg(joinAndTrimColonIfNecessary(d->params));
354  }
355  emit printToNetworksCurrentChatBox(msg, IRCMessageClass::Error);
356  break;
357  }
358 
360  {
361  // Drop the first param.
362  d->params.takeFirst();
363 
364  QString nickname = d->params.takeFirst();
365 
366  emit nicknameInUse(nickname);
367  break;
368  }
369 
372  {
373  d->params.takeFirst(); // User
374  QString channel = d->params.takeFirst();
375  QString reason = joinAndTrimColonIfNecessary(d->params);
376  emit printWithClass(reason, channel, IRCMessageClass(IRCMessageClass::ChannelAction));
377  break;
378  }
379 
380  case IRCResponseType::Join:
381  {
382  QString channel = d->params[0];
383  trimColonIfNecessary(channel);
384 
385  emit userJoinsChannel(channel, d->sender, d->prefix);
386  break;
387  }
388 
389  case IRCResponseType::Kick:
390  {
391  QString channel = d->params.takeFirst();
392  QString whoIsKicked = d->params.takeFirst();
393 
394  QString reason = joinAndTrimColonIfNecessary(d->params);
395 
396  emit kick(channel, d->sender, whoIsKicked, reason);
397  break;
398  }
399 
400  case IRCResponseType::Kill:
401  {
402  QString victim = d->params.takeFirst();
403  QString comment = joinAndTrimColonIfNecessary(d->params);
404  emit kill(victim, comment);
405  break;
406  }
407 
408  case IRCResponseType::Mode:
409  {
410  QString channel = d->params.takeFirst();
411  QString flagsString = d->params.takeFirst();
412 
413  // If there are no more params left on the list then this modes
414  // are for the channel itself. Otherwise they are for the users.
415  if (!d->params.isEmpty())
416  {
417  emit modeInfo(channel, d->sender, flagsString + " " + d->params.join(" "));
418  parseUserModeMessage(channel, flagsString, d->params);
419  }
420 
421  break;
422  }
423 
424  case IRCResponseType::Nick:
425  {
426  QString oldNickname = d->sender;
427  QString newNickname = d->params[0];
428  trimColonIfNecessary(newNickname);
429 
430  emit userChangesNickname(oldNickname, newNickname);
431  break;
432  }
433 
434  case IRCResponseType::Part:
435  {
436  QString farewellMessage = QString();
437  QString channel = d->params[0];
438 
439  if (d->params.size() > 1)
440  {
441  d->params.pop_front();
442 
443  farewellMessage = joinAndTrimColonIfNecessary(d->params);
444  }
445 
446  emit userPartsChannel(channel, d->sender, farewellMessage);
447  break;
448  }
449 
450  case IRCResponseType::Ping:
451  {
452  QString pongToWhom = d->params[0];
453 
454  emit sendPongMessage(pongToWhom);
455  break;
456  }
457 
458  case IRCResponseType::PrivMsg:
459  case IRCResponseType::Notice:
460  parsePrivMsgOrNotice();
461  break;
462 
463  case IRCResponseType::Quit:
464  {
465  QString farewellMessage = QString();
466  farewellMessage = joinAndTrimColonIfNecessary(d->params);
467 
468  emit userQuitsNetwork(d->sender, farewellMessage);
469  break;
470  }
471 
472  case IRCResponseType::Topic:
473  {
474  QString channel = d->params.takeFirst();
475  QString topic = joinAndTrimColonIfNecessary(d->params);
476  QString msg = tr("New topic set by user %1:\n%2").arg(d->sender, topic);
477  emit printWithClass(msg, channel, IRCMessageClass(IRCMessageClass::ChannelAction));
478  break;
479  }
480 
482  {
483  // Messages below 100 are some generic server responses to connect
484  // event.
485  if (responseType.numericType() > 1)
486  {
487  emit print(joinAndTrimColonIfNecessary(d->params), "");
488  return IRCResponseParseResult(responseType, true);
489  }
490 
491  return IRCResponseParseResult(responseType, false);
492  }
493 
494  default:
495  emit parseError(tr(
496  "IRCResponseParser: Type '%1' was recognized but there has been no parse code implemented for it.\
497 (yep, it's a bug in the application!)"
498  ).arg(d->type));
499  return IRCResponseParseResult(responseType, true);
500 
501 
502  }
503 
504  return IRCResponseParseResult(responseType, true);
505 }
506 
507 void IRCResponseParser::parsePrivMsgOrNotice()
508 {
509  if (isPrefixIgnored())
510  {
511  return;
512  }
513  QString recipient = d->params.takeFirst();
514  if (!IRCGlobal::isChannelName(recipient))
515  {
516  // If recipient name is not the channel the
517  // "recipient" QString will point to this client's user.
518  // In order to get a proper recipient we need to use the
519  // "sender" QString instead.
520  recipient = d->sender;
521  }
522 
523  // Join the list to form message contents.
524  QString content = joinAndTrimColonIfNecessary(d->params);
525 
526  IRCResponseType responseType(d->type);
527  IRCCtcpParser::MessageType ctcpMsgType = (responseType == IRCResponseType::Notice) ?
529  IRCCtcpParser ctcp(d->network, d->sender, recipient, content, ctcpMsgType);
530  if (ctcp.parse())
531  {
532  switch (ctcp.echo())
533  {
534  case IRCCtcpParser::PrintAsNormalMessage:
535  emit privMsgLiteralReceived(recipient, ctcp.printable(), IRCMessageClass::Ctcp);
536  break;
537  case IRCCtcpParser::DisplayInServerTab:
538  emit printWithClass(ctcp.printable(), QString(), IRCMessageClass::Ctcp);
539  break;
540  case IRCCtcpParser::DisplayThroughGlobalMessage:
541  emit printToNetworksCurrentChatBox(ctcp.printable(), IRCMessageClass::Ctcp);
542  break;
543  case IRCCtcpParser::DontShow:
544  break;
545  default:
546  gLog << QString("Unhandled CTCP echo type: %1").arg(ctcp.echo());
547  assert(false && "Unhandled CTCP echo type");
548  break;
549  }
550  if (!ctcp.reply().isEmpty() && responseType.type() != IRCResponseType::Notice)
551  {
552  d->network->sendMessage(QString("/NOTICE %1 %2%3%2").arg(d->sender, QChar(0x1), ctcp.reply()));
553  }
554  }
555  else
556  {
557  if (responseType == IRCResponseType::PrivMsg)
558  {
559  emit privMsgReceived(recipient, d->sender, content);
560  }
561  else if (responseType == IRCResponseType::Notice)
562  {
563  emit print(QString("[%1]: %2").arg(d->sender, content), QString());
564  }
565  else
566  {
567  emit parseError(tr("Type '%1' was incorrectly parsed in PrivMsg block.").arg(d->type));
568  }
569  }
570 }
571 
572 void IRCResponseParser::parseUserModeMessage(const QString& channel, QString flagsString, QStringList& nicknames)
573 {
574  // For each flag character there should be one nickname on the list.
575  // If there are less nicknames than characters we will simply abort.
576  // Of course add/subtract characters are not counted here.
577 
578  // The first character should always define the flagMode.
579  FlagModes flagMode = getFlagMode(flagsString[0].toAscii());
580 
581  if (flagMode == FlagModeError)
582  {
583  emit parseError(tr("MODE flags string from IRC server are incorrect: \"%1\". "
584  "Information for channel \"%2\" might not be correct anymore.")
585  .arg(flagsString, channel));
586  return;
587  }
588 
589  for (int i = 1; i < flagsString.size(); ++i)
590  {
591  char flagChar = flagsString[i].toAscii();
592 
593  FlagModes tmpFlagMode = getFlagMode(flagChar);
594  if (tmpFlagMode == FlagModeError)
595  {
596  if (nicknames.empty())
597  {
598  return;
599  }
600 
601  QList<char> addedFlags;
602  QList<char> removedFlags;
603 
604  QString name = nicknames[0];
605 
606  switch (flagMode)
607  {
608  case FlagModeAdd:
609  addedFlags << flagChar;
610  break;
611 
612  case FlagModeRemove:
613  removedFlags << flagChar;
614  break;
615 
616  default:
617  emit parseError(tr("IRCResponseParser::parseUserModeMessage(): "
618  "wrong FlagMode. Information for channel \"%2\" might not be correct anymore."));
619  return;
620  }
621 
622  emit userModeChanged(channel, name, addedFlags, removedFlags);
623  // Drop a name from the list and continue.
624  nicknames.pop_front();
625  }
626  else
627  {
628  flagMode = tmpFlagMode;
629  continue;
630  }
631  }
632 }
633 
634 QString& IRCResponseParser::trimColonIfNecessary(QString& str) const
635 {
636  if (!str.isEmpty() && str[0] == ':')
637  {
638  str.remove(0, 1);
639  }
640 
641  return str;
642 }
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 'as is' 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'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'll treat it the same way we treat RPLWhoIsSpecial.
255 - how many clients on how many servers,
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.