Quassel IRC  Pre-Release
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
ctcpparser.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * Copyright (C) 2005-2015 by the Quassel Project *
3  * devel@quassel-irc.org *
4  * *
5  * This program is free software; you can redistribute it and/or modify *
6  * it under the terms of the GNU General Public License as published by *
7  * the Free Software Foundation; either version 2 of the License, or *
8  * (at your option) version 3. *
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 *
17  * Free Software Foundation, Inc., *
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19  ***************************************************************************/
20 
21 #include "ctcpparser.h"
22 
23 #include "corenetworkconfig.h"
24 #include "coresession.h"
25 #include "ctcpevent.h"
26 #include "messageevent.h"
27 #include "coreuserinputhandler.h"
28 
29 const QByteArray XDELIM = "\001";
30 
31 CtcpParser::CtcpParser(CoreSession *coreSession, QObject *parent)
32  : QObject(parent),
33  _coreSession(coreSession)
34 {
35  QByteArray MQUOTE = QByteArray("\020");
36  _ctcpMDequoteHash[MQUOTE + '0'] = QByteArray(1, '\000');
37  _ctcpMDequoteHash[MQUOTE + 'n'] = QByteArray(1, '\n');
38  _ctcpMDequoteHash[MQUOTE + 'r'] = QByteArray(1, '\r');
39  _ctcpMDequoteHash[MQUOTE + MQUOTE] = MQUOTE;
40 
42 
43  connect(_coreSession->networkConfig(), SIGNAL(standardCtcpSet(bool)), this, SLOT(setStandardCtcp(bool)));
44  connect(this, SIGNAL(newEvent(Event *)), _coreSession->eventManager(), SLOT(postEvent(Event *)));
45 }
46 
47 
48 void CtcpParser::setStandardCtcp(bool enabled)
49 {
50  QByteArray XQUOTE = QByteArray("\134");
51  if (enabled)
52  _ctcpXDelimDequoteHash[XQUOTE + XQUOTE] = XQUOTE;
53  else
54  _ctcpXDelimDequoteHash.remove(XQUOTE + XQUOTE);
55  _ctcpXDelimDequoteHash[XQUOTE + QByteArray("a")] = XDELIM;
56 }
57 
58 
59 void CtcpParser::displayMsg(NetworkEvent *event, Message::Type msgType, const QString &msg, const QString &sender,
60  const QString &target, Message::Flags msgFlags)
61 {
62  if (event->testFlag(EventManager::Silent))
63  return;
64 
65  MessageEvent *msgEvent = new MessageEvent(msgType, event->network(), msg, sender, target, msgFlags);
66  msgEvent->setTimestamp(event->timestamp());
67 
68  emit newEvent(msgEvent);
69 }
70 
71 
72 QByteArray CtcpParser::lowLevelQuote(const QByteArray &message)
73 {
74  QByteArray quotedMessage = message;
75 
76  QHash<QByteArray, QByteArray> quoteHash = _ctcpMDequoteHash;
77  QByteArray MQUOTE = QByteArray("\020");
78  quoteHash.remove(MQUOTE + MQUOTE);
79  quotedMessage.replace(MQUOTE, MQUOTE + MQUOTE);
80 
81  QHash<QByteArray, QByteArray>::const_iterator quoteIter = quoteHash.constBegin();
82  while (quoteIter != quoteHash.constEnd()) {
83  quotedMessage.replace(quoteIter.value(), quoteIter.key());
84  quoteIter++;
85  }
86  return quotedMessage;
87 }
88 
89 
90 QByteArray CtcpParser::lowLevelDequote(const QByteArray &message)
91 {
92  QByteArray dequotedMessage;
93  QByteArray messagepart;
94  QHash<QByteArray, QByteArray>::iterator ctcpquote;
95 
96  // copy dequote Message
97  for (int i = 0; i < message.size(); i++) {
98  messagepart = message.mid(i, 1);
99  if (i+1 < message.size()) {
100  for (ctcpquote = _ctcpMDequoteHash.begin(); ctcpquote != _ctcpMDequoteHash.end(); ++ctcpquote) {
101  if (message.mid(i, 2) == ctcpquote.key()) {
102  messagepart = ctcpquote.value();
103  ++i;
104  break;
105  }
106  }
107  }
108  dequotedMessage += messagepart;
109  }
110  return dequotedMessage;
111 }
112 
113 
114 QByteArray CtcpParser::xdelimQuote(const QByteArray &message)
115 {
116  QByteArray quotedMessage = message;
117  QHash<QByteArray, QByteArray>::const_iterator quoteIter = _ctcpXDelimDequoteHash.constBegin();
118  while (quoteIter != _ctcpXDelimDequoteHash.constEnd()) {
119  quotedMessage.replace(quoteIter.value(), quoteIter.key());
120  quoteIter++;
121  }
122  return quotedMessage;
123 }
124 
125 
126 QByteArray CtcpParser::xdelimDequote(const QByteArray &message)
127 {
128  QByteArray dequotedMessage;
129  QByteArray messagepart;
130  QHash<QByteArray, QByteArray>::iterator xdelimquote;
131 
132  for (int i = 0; i < message.size(); i++) {
133  messagepart = message.mid(i, 1);
134  if (i+1 < message.size()) {
135  for (xdelimquote = _ctcpXDelimDequoteHash.begin(); xdelimquote != _ctcpXDelimDequoteHash.end(); ++xdelimquote) {
136  if (message.mid(i, 2) == xdelimquote.key()) {
137  messagepart = xdelimquote.value();
138  i++;
139  break;
140  }
141  }
142  }
143  dequotedMessage += messagepart;
144  }
145  return dequotedMessage;
146 }
147 
148 
150 {
151  parse(event, Message::Notice);
152 }
153 
154 
156 {
157  parse(event, Message::Plain);
158 }
159 
160 
162 {
163  //lowlevel message dequote
164  QByteArray dequotedMessage = lowLevelDequote(e->rawMessage());
165 
166  CtcpEvent::CtcpType ctcptype = e->type() == EventManager::IrcEventRawNotice
169 
170  Message::Flags flags = (ctcptype == CtcpEvent::Reply && !e->network()->isChannelName(e->target()))
172  : Message::None;
173 
175  parseStandard(e, messagetype, dequotedMessage, ctcptype, flags);
176  else
177  parseSimple(e, messagetype, dequotedMessage, ctcptype, flags);
178 }
179 
180 
181 // only accept CTCPs in their simplest form, i.e. one ctcp, from start to
182 // end, no text around it; not as per the 'specs', but makes people happier
183 void CtcpParser::parseSimple(IrcEventRawMessage *e, Message::Type messagetype, QByteArray dequotedMessage, CtcpEvent::CtcpType ctcptype, Message::Flags flags)
184 {
185  if (dequotedMessage.count(XDELIM) != 2 || dequotedMessage[0] != '\001' || dequotedMessage[dequotedMessage.count() -1] != '\001') {
186  displayMsg(e, messagetype, targetDecode(e, dequotedMessage), e->prefix(), e->target(), flags);
187  } else {
188  int spacePos = -1;
189  QString ctcpcmd, ctcpparam;
190 
191  QByteArray ctcp = xdelimDequote(dequotedMessage.mid(1, dequotedMessage.count() - 2));
192  spacePos = ctcp.indexOf(' ');
193  if (spacePos != -1) {
194  ctcpcmd = targetDecode(e, ctcp.left(spacePos));
195  ctcpparam = targetDecode(e, ctcp.mid(spacePos + 1));
196  } else {
197  ctcpcmd = targetDecode(e, ctcp);
198  ctcpparam = QString();
199  }
200  ctcpcmd = ctcpcmd.toUpper();
201 
202  // we don't want to block /me messages by the CTCP ignore list
203  if (ctcpcmd == QLatin1String("ACTION") || !coreSession()->ignoreListManager()->ctcpMatch(e->prefix(), e->network()->networkName(), ctcpcmd)) {
204  QUuid uuid = QUuid::createUuid();
205  _replies.insert(uuid, CtcpReply(coreNetwork(e), nickFromMask(e->prefix())));
206  CtcpEvent *event = new CtcpEvent(EventManager::CtcpEvent, e->network(), e->prefix(), e->target(),
207  ctcptype, ctcpcmd, ctcpparam, e->timestamp(), uuid);
208  emit newEvent(event);
209  CtcpEvent *flushEvent = new CtcpEvent(EventManager::CtcpEventFlush, e->network(), e->prefix(), e->target(),
210  ctcptype, "INVALID", QString(), e->timestamp(), uuid);
211  emit newEvent(flushEvent);
212  }
213  }
214 }
215 
216 
217 void CtcpParser::parseStandard(IrcEventRawMessage *e, Message::Type messagetype, QByteArray dequotedMessage, CtcpEvent::CtcpType ctcptype, Message::Flags flags)
218 {
219  QByteArray ctcp;
220 
221  QList<CtcpEvent *> ctcpEvents;
222  QUuid uuid; // needed to group all replies together
223 
224  // extract tagged / extended data
225  int xdelimPos = -1;
226  int xdelimEndPos = -1;
227  int spacePos = -1;
228  while ((xdelimPos = dequotedMessage.indexOf(XDELIM)) != -1) {
229  if (xdelimPos > 0)
230  displayMsg(e, messagetype, targetDecode(e, dequotedMessage.left(xdelimPos)), e->prefix(), e->target(), flags);
231 
232  xdelimEndPos = dequotedMessage.indexOf(XDELIM, xdelimPos + 1);
233  if (xdelimEndPos == -1) {
234  // no matching end delimiter found... treat rest of the message as ctcp
235  xdelimEndPos = dequotedMessage.count();
236  }
237  ctcp = xdelimDequote(dequotedMessage.mid(xdelimPos + 1, xdelimEndPos - xdelimPos - 1));
238  dequotedMessage = dequotedMessage.mid(xdelimEndPos + 1);
239 
240  //dispatch the ctcp command
241  QString ctcpcmd = targetDecode(e, ctcp.left(spacePos));
242  QString ctcpparam = targetDecode(e, ctcp.mid(spacePos + 1));
243 
244  spacePos = ctcp.indexOf(' ');
245  if (spacePos != -1) {
246  ctcpcmd = targetDecode(e, ctcp.left(spacePos));
247  ctcpparam = targetDecode(e, ctcp.mid(spacePos + 1));
248  }
249  else {
250  ctcpcmd = targetDecode(e, ctcp);
251  ctcpparam = QString();
252  }
253 
254  ctcpcmd = ctcpcmd.toUpper();
255 
256  // we don't want to block /me messages by the CTCP ignore list
257  if (ctcpcmd == QLatin1String("ACTION") || !coreSession()->ignoreListManager()->ctcpMatch(e->prefix(), e->network()->networkName(), ctcpcmd)) {
258  if (uuid.isNull())
259  uuid = QUuid::createUuid();
260 
261  CtcpEvent *event = new CtcpEvent(EventManager::CtcpEvent, e->network(), e->prefix(), e->target(),
262  ctcptype, ctcpcmd, ctcpparam, e->timestamp(), uuid);
263  ctcpEvents << event;
264  }
265  }
266  if (!ctcpEvents.isEmpty()) {
267  _replies.insert(uuid, CtcpReply(coreNetwork(e), nickFromMask(e->prefix())));
268  CtcpEvent *flushEvent = new CtcpEvent(EventManager::CtcpEventFlush, e->network(), e->prefix(), e->target(),
269  ctcptype, "INVALID", QString(), e->timestamp(), uuid);
270  ctcpEvents << flushEvent;
271  foreach(CtcpEvent *event, ctcpEvents) {
272  emit newEvent(event);
273  }
274  }
275 
276  if (!dequotedMessage.isEmpty())
277  displayMsg(e, messagetype, targetDecode(e, dequotedMessage), e->prefix(), e->target(), flags);
278 }
279 
280 
282 {
283  CoreNetwork *net = coreNetwork(e);
284  if (e->type() == EventManager::CtcpEvent) {
285  QByteArray quotedReply;
286  QString bufname = nickFromMask(e->prefix());
287  if (e->ctcpType() == CtcpEvent::Query && !e->reply().isNull()) {
288  if (_replies.contains(e->uuid()))
289  _replies[e->uuid()].replies << lowLevelQuote(pack(net->serverEncode(e->ctcpCmd()),
290  net->userEncode(bufname, e->reply())));
291  else
292  // reply not caused by a request processed in here, so send it off immediately
293  reply(net, bufname, e->ctcpCmd(), e->reply());
294  }
295  }
296  else if (e->type() == EventManager::CtcpEventFlush && _replies.contains(e->uuid())) {
297  CtcpReply reply = _replies.take(e->uuid());
298  if (reply.replies.count())
299  packedReply(net, reply.bufferName, reply.replies);
300  }
301 }
302 
303 
304 QByteArray CtcpParser::pack(const QByteArray &ctcpTag, const QByteArray &message)
305 {
306  if (message.isEmpty())
307  return XDELIM + ctcpTag + XDELIM;
308 
309  return XDELIM + ctcpTag + ' ' + xdelimQuote(message) + XDELIM;
310 }
311 
312 
313 void CtcpParser::query(CoreNetwork *net, const QString &bufname, const QString &ctcpTag, const QString &message)
314 {
315  QString cmd("PRIVMSG");
316 
317  std::function<QList<QByteArray>(QString &)> cmdGenerator = [&] (QString &splitMsg) -> QList<QByteArray> {
318  return QList<QByteArray>() << net->serverEncode(bufname) << lowLevelQuote(pack(net->serverEncode(ctcpTag), net->userEncode(bufname, splitMsg)));
319  };
320 
321  net->putCmd(cmd, net->splitMessage(cmd, message, cmdGenerator));
322 }
323 
324 
325 void CtcpParser::reply(CoreNetwork *net, const QString &bufname, const QString &ctcpTag, const QString &message)
326 {
327  QList<QByteArray> params;
328  params << net->serverEncode(bufname) << lowLevelQuote(pack(net->serverEncode(ctcpTag), net->userEncode(bufname, message)));
329  net->putCmd("NOTICE", params);
330 }
331 
332 
333 void CtcpParser::packedReply(CoreNetwork *net, const QString &bufname, const QList<QByteArray> &replies)
334 {
335  QList<QByteArray> params;
336 
337  int answerSize = 0;
338  for (int i = 0; i < replies.count(); i++) {
339  answerSize += replies.at(i).size();
340  }
341 
342  QByteArray quotedReply;
343  quotedReply.reserve(answerSize);
344  for (int i = 0; i < replies.count(); i++) {
345  quotedReply.append(replies.at(i));
346  }
347 
348  params << net->serverEncode(bufname) << quotedReply;
349  // FIXME user proper event
350  net->putCmd("NOTICE", params);
351 }