1 /***************************************************************************
2 * Copyright (C) 2005-2013 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
21 #include "ctcpparser.h"
23 #include "corenetworkconfig.h"
24 #include "coresession.h"
25 #include "ctcpevent.h"
26 #include "messageevent.h"
28 const QByteArray XDELIM = "\001";
30 CtcpParser::CtcpParser(CoreSession *coreSession, QObject *parent)
32 _coreSession(coreSession)
34 QByteArray MQUOTE = QByteArray("\020");
35 _ctcpMDequoteHash[MQUOTE + '0'] = QByteArray(1, '\000');
36 _ctcpMDequoteHash[MQUOTE + 'n'] = QByteArray(1, '\n');
37 _ctcpMDequoteHash[MQUOTE + 'r'] = QByteArray(1, '\r');
38 _ctcpMDequoteHash[MQUOTE + MQUOTE] = MQUOTE;
40 QByteArray XQUOTE = QByteArray("\134");
41 _ctcpXDelimDequoteHash[XQUOTE + XQUOTE] = XQUOTE;
42 _ctcpXDelimDequoteHash[XQUOTE + QByteArray("a")] = XDELIM;
44 connect(this, SIGNAL(newEvent(Event *)), _coreSession->eventManager(), SLOT(postEvent(Event *)));
48 void CtcpParser::displayMsg(NetworkEvent *event, Message::Type msgType, const QString &msg, const QString &sender,
49 const QString &target, Message::Flags msgFlags)
51 if (event->testFlag(EventManager::Silent))
54 MessageEvent *msgEvent = new MessageEvent(msgType, event->network(), msg, sender, target, msgFlags);
55 msgEvent->setTimestamp(event->timestamp());
57 emit newEvent(msgEvent);
61 QByteArray CtcpParser::lowLevelQuote(const QByteArray &message)
63 QByteArray quotedMessage = message;
65 QHash<QByteArray, QByteArray> quoteHash = _ctcpMDequoteHash;
66 QByteArray MQUOTE = QByteArray("\020");
67 quoteHash.remove(MQUOTE + MQUOTE);
68 quotedMessage.replace(MQUOTE, MQUOTE + MQUOTE);
70 QHash<QByteArray, QByteArray>::const_iterator quoteIter = quoteHash.constBegin();
71 while (quoteIter != quoteHash.constEnd()) {
72 quotedMessage.replace(quoteIter.value(), quoteIter.key());
79 QByteArray CtcpParser::lowLevelDequote(const QByteArray &message)
81 QByteArray dequotedMessage;
82 QByteArray messagepart;
83 QHash<QByteArray, QByteArray>::iterator ctcpquote;
85 // copy dequote Message
86 for (int i = 0; i < message.size(); i++) {
87 messagepart = message.mid(i, 1);
88 if (i+1 < message.size()) {
89 for (ctcpquote = _ctcpMDequoteHash.begin(); ctcpquote != _ctcpMDequoteHash.end(); ++ctcpquote) {
90 if (message.mid(i, 2) == ctcpquote.key()) {
91 messagepart = ctcpquote.value();
97 dequotedMessage += messagepart;
99 return dequotedMessage;
103 QByteArray CtcpParser::xdelimQuote(const QByteArray &message)
105 QByteArray quotedMessage = message;
106 QHash<QByteArray, QByteArray>::const_iterator quoteIter = _ctcpXDelimDequoteHash.constBegin();
107 while (quoteIter != _ctcpXDelimDequoteHash.constEnd()) {
108 quotedMessage.replace(quoteIter.value(), quoteIter.key());
111 return quotedMessage;
115 QByteArray CtcpParser::xdelimDequote(const QByteArray &message)
117 QByteArray dequotedMessage;
118 QByteArray messagepart;
119 QHash<QByteArray, QByteArray>::iterator xdelimquote;
121 for (int i = 0; i < message.size(); i++) {
122 messagepart = message.mid(i, 1);
123 if (i+1 < message.size()) {
124 for (xdelimquote = _ctcpXDelimDequoteHash.begin(); xdelimquote != _ctcpXDelimDequoteHash.end(); ++xdelimquote) {
125 if (message.mid(i, 2) == xdelimquote.key()) {
126 messagepart = xdelimquote.value();
132 dequotedMessage += messagepart;
134 return dequotedMessage;
138 void CtcpParser::processIrcEventRawNotice(IrcEventRawMessage *event)
140 parse(event, Message::Notice);
144 void CtcpParser::processIrcEventRawPrivmsg(IrcEventRawMessage *event)
146 parse(event, Message::Plain);
150 void CtcpParser::parse(IrcEventRawMessage *e, Message::Type messagetype)
152 //lowlevel message dequote
153 QByteArray dequotedMessage = lowLevelDequote(e->rawMessage());
155 CtcpEvent::CtcpType ctcptype = e->type() == EventManager::IrcEventRawNotice
159 Message::Flags flags = (ctcptype == CtcpEvent::Reply && !e->network()->isChannelName(e->target()))
160 ? Message::Redirected
163 if (coreSession()->networkConfig()->standardCtcp())
164 parseStandard(e, messagetype, dequotedMessage, ctcptype, flags);
166 parseSimple(e, messagetype, dequotedMessage, ctcptype, flags);
170 // only accept CTCPs in their simplest form, i.e. one ctcp, from start to
171 // end, no text around it; not as per the 'specs', but makes people happier
172 void CtcpParser::parseSimple(IrcEventRawMessage *e, Message::Type messagetype, QByteArray dequotedMessage, CtcpEvent::CtcpType ctcptype, Message::Flags flags)
174 if (dequotedMessage.count(XDELIM) != 2 || dequotedMessage[0] != '\001' || dequotedMessage[dequotedMessage.count() -1] != '\001') {
175 displayMsg(e, messagetype, targetDecode(e, dequotedMessage), e->prefix(), e->target(), flags);
178 QString ctcpcmd, ctcpparam;
180 QByteArray ctcp = xdelimDequote(dequotedMessage.mid(1, dequotedMessage.count() - 2));
181 spacePos = ctcp.indexOf(' ');
182 if (spacePos != -1) {
183 ctcpcmd = targetDecode(e, ctcp.left(spacePos));
184 ctcpparam = targetDecode(e, ctcp.mid(spacePos + 1));
186 ctcpcmd = targetDecode(e, ctcp);
187 ctcpparam = QString();
189 ctcpcmd = ctcpcmd.toUpper();
191 // we don't want to block /me messages by the CTCP ignore list
192 if (ctcpcmd == QLatin1String("ACTION") || !coreSession()->ignoreListManager()->ctcpMatch(e->prefix(), e->network()->networkName(), ctcpcmd)) {
193 QUuid uuid = QUuid::createUuid();
194 _replies.insert(uuid, CtcpReply(coreNetwork(e), nickFromMask(e->prefix())));
195 CtcpEvent *event = new CtcpEvent(EventManager::CtcpEvent, e->network(), e->prefix(), e->target(),
196 ctcptype, ctcpcmd, ctcpparam, e->timestamp(), uuid);
197 emit newEvent(event);
198 CtcpEvent *flushEvent = new CtcpEvent(EventManager::CtcpEventFlush, e->network(), e->prefix(), e->target(),
199 ctcptype, "INVALID", QString(), e->timestamp(), uuid);
200 emit newEvent(flushEvent);
206 void CtcpParser::parseStandard(IrcEventRawMessage *e, Message::Type messagetype, QByteArray dequotedMessage, CtcpEvent::CtcpType ctcptype, Message::Flags flags)
210 QList<CtcpEvent *> ctcpEvents;
211 QUuid uuid; // needed to group all replies together
213 // extract tagged / extended data
215 int xdelimEndPos = -1;
217 while ((xdelimPos = dequotedMessage.indexOf(XDELIM)) != -1) {
219 displayMsg(e, messagetype, targetDecode(e, dequotedMessage.left(xdelimPos)), e->prefix(), e->target(), flags);
221 xdelimEndPos = dequotedMessage.indexOf(XDELIM, xdelimPos + 1);
222 if (xdelimEndPos == -1) {
223 // no matching end delimiter found... treat rest of the message as ctcp
224 xdelimEndPos = dequotedMessage.count();
226 ctcp = xdelimDequote(dequotedMessage.mid(xdelimPos + 1, xdelimEndPos - xdelimPos - 1));
227 dequotedMessage = dequotedMessage.mid(xdelimEndPos + 1);
229 //dispatch the ctcp command
230 QString ctcpcmd = targetDecode(e, ctcp.left(spacePos));
231 QString ctcpparam = targetDecode(e, ctcp.mid(spacePos + 1));
233 spacePos = ctcp.indexOf(' ');
234 if (spacePos != -1) {
235 ctcpcmd = targetDecode(e, ctcp.left(spacePos));
236 ctcpparam = targetDecode(e, ctcp.mid(spacePos + 1));
239 ctcpcmd = targetDecode(e, ctcp);
240 ctcpparam = QString();
243 ctcpcmd = ctcpcmd.toUpper();
245 // we don't want to block /me messages by the CTCP ignore list
246 if (ctcpcmd == QLatin1String("ACTION") || !coreSession()->ignoreListManager()->ctcpMatch(e->prefix(), e->network()->networkName(), ctcpcmd)) {
248 uuid = QUuid::createUuid();
250 CtcpEvent *event = new CtcpEvent(EventManager::CtcpEvent, e->network(), e->prefix(), e->target(),
251 ctcptype, ctcpcmd, ctcpparam, e->timestamp(), uuid);
255 if (!ctcpEvents.isEmpty()) {
256 _replies.insert(uuid, CtcpReply(coreNetwork(e), nickFromMask(e->prefix())));
257 CtcpEvent *flushEvent = new CtcpEvent(EventManager::CtcpEventFlush, e->network(), e->prefix(), e->target(),
258 ctcptype, "INVALID", QString(), e->timestamp(), uuid);
259 ctcpEvents << flushEvent;
260 foreach(CtcpEvent *event, ctcpEvents) {
261 emit newEvent(event);
265 if (!dequotedMessage.isEmpty())
266 displayMsg(e, messagetype, targetDecode(e, dequotedMessage), e->prefix(), e->target(), flags);
270 void CtcpParser::sendCtcpEvent(CtcpEvent *e)
272 CoreNetwork *net = coreNetwork(e);
273 if (e->type() == EventManager::CtcpEvent) {
274 QByteArray quotedReply;
275 QString bufname = nickFromMask(e->prefix());
276 if (e->ctcpType() == CtcpEvent::Query && !e->reply().isNull()) {
277 if (_replies.contains(e->uuid()))
278 _replies[e->uuid()].replies << lowLevelQuote(pack(net->serverEncode(e->ctcpCmd()),
279 net->userEncode(bufname, e->reply())));
281 // reply not caused by a request processed in here, so send it off immediately
282 reply(net, bufname, e->ctcpCmd(), e->reply());
285 else if (e->type() == EventManager::CtcpEventFlush && _replies.contains(e->uuid())) {
286 CtcpReply reply = _replies.take(e->uuid());
287 if (reply.replies.count())
288 packedReply(net, reply.bufferName, reply.replies);
293 QByteArray CtcpParser::pack(const QByteArray &ctcpTag, const QByteArray &message)
295 if (message.isEmpty())
296 return XDELIM + ctcpTag + XDELIM;
298 return XDELIM + ctcpTag + ' ' + xdelimQuote(message) + XDELIM;
302 void CtcpParser::query(CoreNetwork *net, const QString &bufname, const QString &ctcpTag, const QString &message)
304 QList<QByteArray> params;
305 params << net->serverEncode(bufname) << lowLevelQuote(pack(net->serverEncode(ctcpTag), net->userEncode(bufname, message)));
306 net->putCmd("PRIVMSG", params);
310 void CtcpParser::reply(CoreNetwork *net, const QString &bufname, const QString &ctcpTag, const QString &message)
312 QList<QByteArray> params;
313 params << net->serverEncode(bufname) << lowLevelQuote(pack(net->serverEncode(ctcpTag), net->userEncode(bufname, message)));
314 net->putCmd("NOTICE", params);
318 void CtcpParser::packedReply(CoreNetwork *net, const QString &bufname, const QList<QByteArray> &replies)
320 QList<QByteArray> params;
323 for (int i = 0; i < replies.count(); i++) {
324 answerSize += replies.at(i).size();
327 QByteArray quotedReply;
328 quotedReply.reserve(answerSize);
329 for (int i = 0; i < replies.count(); i++) {
330 quotedReply.append(replies.at(i));
333 params << net->serverEncode(bufname) << quotedReply;
334 // FIXME user proper event
335 net->putCmd("NOTICE", params);