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 }
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:55
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 successfully joins the network.
A question is being asked through PRIVMSG.
Definition: ircctcpparser.h:51
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.