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