cddb6e18b743070437deeec2c8c6142f7658f038
[quassel.git] / src / core / ctcpparser.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2013 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 "coresession.h"
24 #include "ctcpevent.h"
25 #include "messageevent.h"
26
27 const QByteArray XDELIM = "\001";
28
29 CtcpParser::CtcpParser(CoreSession *coreSession, QObject *parent)
30     : QObject(parent),
31     _coreSession(coreSession)
32 {
33     QByteArray MQUOTE = QByteArray("\020");
34     _ctcpMDequoteHash[MQUOTE + '0'] = QByteArray(1, '\000');
35     _ctcpMDequoteHash[MQUOTE + 'n'] = QByteArray(1, '\n');
36     _ctcpMDequoteHash[MQUOTE + 'r'] = QByteArray(1, '\r');
37     _ctcpMDequoteHash[MQUOTE + MQUOTE] = MQUOTE;
38
39     QByteArray XQUOTE = QByteArray("\134");
40     _ctcpXDelimDequoteHash[XQUOTE + XQUOTE] = XQUOTE;
41     _ctcpXDelimDequoteHash[XQUOTE + QByteArray("a")] = XDELIM;
42
43     connect(this, SIGNAL(newEvent(Event *)), _coreSession->eventManager(), SLOT(postEvent(Event *)));
44 }
45
46
47 void CtcpParser::displayMsg(NetworkEvent *event, Message::Type msgType, const QString &msg, const QString &sender,
48     const QString &target, Message::Flags msgFlags)
49 {
50     if (event->testFlag(EventManager::Silent))
51         return;
52
53     MessageEvent *msgEvent = new MessageEvent(msgType, event->network(), msg, sender, target, msgFlags);
54     msgEvent->setTimestamp(event->timestamp());
55
56     emit newEvent(msgEvent);
57 }
58
59
60 QByteArray CtcpParser::lowLevelQuote(const QByteArray &message)
61 {
62     QByteArray quotedMessage = message;
63
64     QHash<QByteArray, QByteArray> quoteHash = _ctcpMDequoteHash;
65     QByteArray MQUOTE = QByteArray("\020");
66     quoteHash.remove(MQUOTE + MQUOTE);
67     quotedMessage.replace(MQUOTE, MQUOTE + MQUOTE);
68
69     QHash<QByteArray, QByteArray>::const_iterator quoteIter = quoteHash.constBegin();
70     while (quoteIter != quoteHash.constEnd()) {
71         quotedMessage.replace(quoteIter.value(), quoteIter.key());
72         quoteIter++;
73     }
74     return quotedMessage;
75 }
76
77
78 QByteArray CtcpParser::lowLevelDequote(const QByteArray &message)
79 {
80     QByteArray dequotedMessage;
81     QByteArray messagepart;
82     QHash<QByteArray, QByteArray>::iterator ctcpquote;
83
84     // copy dequote Message
85     for (int i = 0; i < message.size(); i++) {
86         messagepart = message.mid(i, 1);
87         if (i+1 < message.size()) {
88             for (ctcpquote = _ctcpMDequoteHash.begin(); ctcpquote != _ctcpMDequoteHash.end(); ++ctcpquote) {
89                 if (message.mid(i, 2) == ctcpquote.key()) {
90                     messagepart = ctcpquote.value();
91                     ++i;
92                     break;
93                 }
94             }
95         }
96         dequotedMessage += messagepart;
97     }
98     return dequotedMessage;
99 }
100
101
102 QByteArray CtcpParser::xdelimQuote(const QByteArray &message)
103 {
104     QByteArray quotedMessage = message;
105     QHash<QByteArray, QByteArray>::const_iterator quoteIter = _ctcpXDelimDequoteHash.constBegin();
106     while (quoteIter != _ctcpXDelimDequoteHash.constEnd()) {
107         quotedMessage.replace(quoteIter.value(), quoteIter.key());
108         quoteIter++;
109     }
110     return quotedMessage;
111 }
112
113
114 QByteArray CtcpParser::xdelimDequote(const QByteArray &message)
115 {
116     QByteArray dequotedMessage;
117     QByteArray messagepart;
118     QHash<QByteArray, QByteArray>::iterator xdelimquote;
119
120     for (int i = 0; i < message.size(); i++) {
121         messagepart = message.mid(i, 1);
122         if (i+1 < message.size()) {
123             for (xdelimquote = _ctcpXDelimDequoteHash.begin(); xdelimquote != _ctcpXDelimDequoteHash.end(); ++xdelimquote) {
124                 if (message.mid(i, 2) == xdelimquote.key()) {
125                     messagepart = xdelimquote.value();
126                     i++;
127                     break;
128                 }
129             }
130         }
131         dequotedMessage += messagepart;
132     }
133     return dequotedMessage;
134 }
135
136
137 void CtcpParser::processIrcEventRawNotice(IrcEventRawMessage *event)
138 {
139     parse(event, Message::Notice);
140 }
141
142
143 void CtcpParser::processIrcEventRawPrivmsg(IrcEventRawMessage *event)
144 {
145     parse(event, Message::Plain);
146 }
147
148
149 void CtcpParser::parse(IrcEventRawMessage *e, Message::Type messagetype)
150 {
151     QByteArray ctcp;
152
153     //lowlevel message dequote
154     QByteArray dequotedMessage = lowLevelDequote(e->rawMessage());
155
156     CtcpEvent::CtcpType ctcptype = e->type() == EventManager::IrcEventRawNotice
157                                    ? CtcpEvent::Reply
158                                    : CtcpEvent::Query;
159
160     Message::Flags flags = (ctcptype == CtcpEvent::Reply && !e->network()->isChannelName(e->target()))
161                            ? Message::Redirected
162                            : Message::None;
163
164     QList<CtcpEvent *> ctcpEvents;
165     QUuid uuid; // needed to group all replies together
166
167     // extract tagged / extended data
168     int xdelimPos = -1;
169     int xdelimEndPos = -1;
170     int spacePos = -1;
171     while ((xdelimPos = dequotedMessage.indexOf(XDELIM)) != -1) {
172         if (xdelimPos > 0)
173             displayMsg(e, messagetype, targetDecode(e, dequotedMessage.left(xdelimPos)), e->prefix(), e->target(), flags);
174
175         xdelimEndPos = dequotedMessage.indexOf(XDELIM, xdelimPos + 1);
176         if (xdelimEndPos == -1) {
177             // no matching end delimiter found... treat rest of the message as ctcp
178             xdelimEndPos = dequotedMessage.count();
179         }
180         ctcp = xdelimDequote(dequotedMessage.mid(xdelimPos + 1, xdelimEndPos - xdelimPos - 1));
181         dequotedMessage = dequotedMessage.mid(xdelimEndPos + 1);
182
183         //dispatch the ctcp command
184         QString ctcpcmd = targetDecode(e, ctcp.left(spacePos));
185         QString ctcpparam = targetDecode(e, ctcp.mid(spacePos + 1));
186
187         spacePos = ctcp.indexOf(' ');
188         if (spacePos != -1) {
189             ctcpcmd = targetDecode(e, ctcp.left(spacePos));
190             ctcpparam = targetDecode(e, ctcp.mid(spacePos + 1));
191         }
192         else {
193             ctcpcmd = targetDecode(e, ctcp);
194             ctcpparam = QString();
195         }
196
197         ctcpcmd = ctcpcmd.toUpper();
198
199         // we don't want to block /me messages by the CTCP ignore list
200         if (ctcpcmd == QLatin1String("ACTION") || !coreSession()->ignoreListManager()->ctcpMatch(e->prefix(), e->network()->networkName(), ctcpcmd)) {
201             if (uuid.isNull())
202                 uuid = QUuid::createUuid();
203
204             CtcpEvent *event = new CtcpEvent(EventManager::CtcpEvent, e->network(), e->prefix(), e->target(),
205                 ctcptype, ctcpcmd, ctcpparam, e->timestamp(), uuid);
206             ctcpEvents << event;
207         }
208     }
209     if (!ctcpEvents.isEmpty()) {
210         _replies.insert(uuid, CtcpReply(coreNetwork(e), nickFromMask(e->prefix())));
211         CtcpEvent *flushEvent = new CtcpEvent(EventManager::CtcpEventFlush, e->network(), e->prefix(), e->target(),
212             ctcptype, "INVALID", QString(), e->timestamp(), uuid);
213         ctcpEvents << flushEvent;
214         foreach(CtcpEvent *event, ctcpEvents) {
215             emit newEvent(event);
216         }
217     }
218
219     if (!dequotedMessage.isEmpty())
220         displayMsg(e, messagetype, targetDecode(e, dequotedMessage), e->prefix(), e->target(), flags);
221 }
222
223
224 void CtcpParser::sendCtcpEvent(CtcpEvent *e)
225 {
226     CoreNetwork *net = coreNetwork(e);
227     if (e->type() == EventManager::CtcpEvent) {
228         QByteArray quotedReply;
229         QString bufname = nickFromMask(e->prefix());
230         if (e->ctcpType() == CtcpEvent::Query && !e->reply().isNull()) {
231             if (_replies.contains(e->uuid()))
232                 _replies[e->uuid()].replies << lowLevelQuote(pack(net->serverEncode(e->ctcpCmd()),
233                         net->userEncode(bufname, e->reply())));
234             else
235                 // reply not caused by a request processed in here, so send it off immediately
236                 reply(net, bufname, e->ctcpCmd(), e->reply());
237         }
238     }
239     else if (e->type() == EventManager::CtcpEventFlush && _replies.contains(e->uuid())) {
240         CtcpReply reply = _replies.take(e->uuid());
241         if (reply.replies.count())
242             packedReply(net, reply.bufferName, reply.replies);
243     }
244 }
245
246
247 QByteArray CtcpParser::pack(const QByteArray &ctcpTag, const QByteArray &message)
248 {
249     if (message.isEmpty())
250         return XDELIM + ctcpTag + XDELIM;
251
252     return XDELIM + ctcpTag + ' ' + xdelimQuote(message) + XDELIM;
253 }
254
255
256 void CtcpParser::query(CoreNetwork *net, const QString &bufname, const QString &ctcpTag, const QString &message)
257 {
258     QList<QByteArray> params;
259     params << net->serverEncode(bufname) << lowLevelQuote(pack(net->serverEncode(ctcpTag), net->userEncode(bufname, message)));
260     net->putCmd("PRIVMSG", params);
261 }
262
263
264 void CtcpParser::reply(CoreNetwork *net, const QString &bufname, const QString &ctcpTag, const QString &message)
265 {
266     QList<QByteArray> params;
267     params << net->serverEncode(bufname) << lowLevelQuote(pack(net->serverEncode(ctcpTag), net->userEncode(bufname, message)));
268     net->putCmd("NOTICE", params);
269 }
270
271
272 void CtcpParser::packedReply(CoreNetwork *net, const QString &bufname, const QList<QByteArray> &replies)
273 {
274     QList<QByteArray> params;
275
276     int answerSize = 0;
277     for (int i = 0; i < replies.count(); i++) {
278         answerSize += replies.at(i).size();
279     }
280
281     QByteArray quotedReply;
282     quotedReply.reserve(answerSize);
283     for (int i = 0; i < replies.count(); i++) {
284         quotedReply.append(replies.at(i));
285     }
286
287     params << net->serverEncode(bufname) << quotedReply;
288     // FIXME user proper event
289     net->putCmd("NOTICE", params);
290 }