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 "ircparser.h" |
22 | |
23 | #include "corenetwork.h" |
24 | #include "eventmanager.h" |
25 | #include "ircevent.h" |
26 | #include "messageevent.h" |
27 | #include "networkevent.h" |
28 | |
29 | #ifdef HAVE_QCA2 |
30 | # include "cipher.h" |
31 | # include "keyevent.h" |
32 | #endif |
33 | |
34 | IrcParser::IrcParser(CoreSession *session) : |
35 | QObject(session), |
36 | _coreSession(session) |
37 | { |
38 | connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *))); |
39 | } |
40 | |
41 | |
42 | bool IrcParser::checkParamCount(const QString &cmd, const QList<QByteArray> ¶ms, int minParams) |
43 | { |
44 | if (params.count() < minParams) { |
45 | qWarning() << "Expected" << minParams << "params for IRC command" << cmd << ", got:" << params; |
46 | return false; |
47 | } |
48 | return true; |
49 | } |
50 | |
51 | |
52 | QByteArray IrcParser::decrypt(Network *network, const QString &bufferName, const QByteArray &message, bool isTopic) |
53 | { |
54 | #ifdef HAVE_QCA2 |
55 | if (message.isEmpty()) |
56 | return message; |
57 | |
58 | if (!Cipher::neededFeaturesAvailable()) |
59 | return message; |
60 | |
61 | Cipher *cipher = qobject_cast<CoreNetwork *>(network)->cipher(bufferName); |
62 | if (!cipher || cipher->key().isEmpty()) |
63 | return message; |
64 | |
65 | return isTopic ? cipher->decryptTopic(message) : cipher->decrypt(message); |
66 | #else |
67 | Q_UNUSED(network); |
68 | Q_UNUSED(bufferName); |
69 | Q_UNUSED(isTopic); |
70 | return message; |
71 | #endif |
72 | } |
73 | |
74 | |
75 | /* parse the raw server string and generate an appropriate event */ |
76 | /* used to be handleServerMsg() */ |
77 | void IrcParser::processNetworkIncoming(NetworkDataEvent *e) |
78 | { |
79 | CoreNetwork *net = qobject_cast<CoreNetwork *>(e->network()); |
80 | if (!net) { |
81 | qWarning() << "Received network event without valid network pointer!" ; |
82 | return; |
83 | } |
84 | |
85 | // note that the IRC server is still alive |
86 | net->resetPingTimeout(); |
87 | |
88 | QByteArray msg = e->data(); |
89 | if (msg.isEmpty()) { |
90 | qWarning() << "Received empty string from server!" ; |
91 | return; |
92 | } |
93 | |
94 | // Now we split the raw message into its various parts... |
95 | QString prefix; |
96 | QByteArray trailing; |
97 | QString cmd, target; |
98 | |
99 | // First, check for a trailing parameter introduced by " :", since this might screw up splitting the msg |
100 | // NOTE: This assumes that this is true in raw encoding, but well, hopefully there are no servers running in japanese on protocol level... |
101 | int idx = msg.indexOf(" :" ); |
102 | if (idx >= 0) { |
103 | if (msg.length() > idx + 2) |
104 | trailing = msg.mid(idx + 2); |
105 | msg = msg.left(idx); |
106 | } |
107 | // OK, now it is safe to split... |
108 | QList<QByteArray> params = msg.split(' '); |
109 | |
110 | // This could still contain empty elements due to (faulty?) ircds sending multiple spaces in a row |
111 | // Also, QByteArray is not nearly as convenient to work with as QString for such things :) |
112 | QList<QByteArray>::iterator iter = params.begin(); |
113 | while (iter != params.end()) { |
114 | if (iter->isEmpty()) |
115 | iter = params.erase(iter); |
116 | else |
117 | ++iter; |
118 | } |
119 | |
120 | if (!trailing.isEmpty()) |
121 | params << trailing; |
122 | if (params.count() < 1) { |
123 | qWarning() << "Received invalid string from server!" ; |
124 | return; |
125 | } |
126 | |
127 | QString foo = net->serverDecode(params.takeFirst()); |
128 | |
129 | // a colon as the first chars indicates the existence of a prefix |
130 | if (foo[0] == ':') { |
131 | foo.remove(0, 1); |
132 | prefix = foo; |
133 | if (params.count() < 1) { |
134 | qWarning() << "Received invalid string from server!" ; |
135 | return; |
136 | } |
137 | foo = net->serverDecode(params.takeFirst()); |
138 | } |
139 | |
140 | // next string without a whitespace is the command |
141 | cmd = foo.trimmed(); |
142 | |
143 | QList<Event *> events; |
144 | EventManager::EventType type = EventManager::Invalid; |
145 | |
146 | uint num = cmd.toUInt(); |
147 | if (num > 0) { |
148 | // numeric reply |
149 | if (params.count() == 0) { |
150 | qWarning() << "Message received from server violates RFC and is ignored!" << msg; |
151 | return; |
152 | } |
153 | // numeric replies have the target as first param (RFC 2812 - 2.4). this is usually our own nick. Remove this! |
154 | target = net->serverDecode(params.takeFirst()); |
155 | type = EventManager::IrcEventNumeric; |
156 | } |
157 | else { |
158 | // any other irc command |
159 | QString typeName = QLatin1String("IrcEvent" ) + cmd.at(0).toUpper() + cmd.mid(1).toLower(); |
160 | type = eventManager()->eventTypeByName(typeName); |
161 | if (type == EventManager::Invalid) { |
162 | type = eventManager()->eventTypeByName("IrcEventUnknown" ); |
163 | Q_ASSERT(type != EventManager::Invalid); |
164 | } |
165 | target = QString(); |
166 | } |
167 | |
168 | // Almost always, all params are server-encoded. There's a few exceptions, let's catch them here! |
169 | // Possibly not the best option, we might want something more generic? Maybe yet another layer of |
170 | // unencoded events with event handlers for the exceptions... |
171 | // Also, PRIVMSG and NOTICE need some special handling, we put this in here as well, so we get out |
172 | // nice pre-parsed events that the CTCP handler can consume. |
173 | |
174 | QStringList decParams; |
175 | bool defaultHandling = true; // whether to automatically copy the remaining params and send the event |
176 | |
177 | switch (type) { |
178 | case EventManager::IrcEventPrivmsg: |
179 | defaultHandling = false; // this might create a list of events |
180 | |
181 | if (checkParamCount(cmd, params, 1)) { |
182 | QString senderNick = nickFromMask(prefix); |
183 | QByteArray msg = params.count() < 2 ? QByteArray() : params.at(1); |
184 | |
185 | QStringList targets = net->serverDecode(params.at(0)).split(',', QString::SkipEmptyParts); |
186 | QStringList::const_iterator targetIter; |
187 | for (targetIter = targets.constBegin(); targetIter != targets.constEnd(); ++targetIter) { |
188 | QString target = net->isChannelName(*targetIter) ? *targetIter : senderNick; |
189 | |
190 | msg = decrypt(net, target, msg); |
191 | |
192 | events << new IrcEventRawMessage(EventManager::IrcEventRawPrivmsg, net, msg, prefix, target, e->timestamp()); |
193 | } |
194 | } |
195 | break; |
196 | |
197 | case EventManager::IrcEventNotice: |
198 | defaultHandling = false; |
199 | |
200 | if (checkParamCount(cmd, params, 2)) { |
201 | QStringList targets = net->serverDecode(params.at(0)).split(',', QString::SkipEmptyParts); |
202 | QStringList::const_iterator targetIter; |
203 | for (targetIter = targets.constBegin(); targetIter != targets.constEnd(); ++targetIter) { |
204 | QString target = *targetIter; |
205 | |
206 | // special treatment for welcome messages like: |
207 | // :ChanServ!ChanServ@services. NOTICE egst :[#apache] Welcome, this is #apache. Please read the in-channel topic message. This channel is being logged by IRSeekBot. If you have any question please see http://blog.freenode.net/?p=68 |
208 | if (!net->isChannelName(target)) { |
209 | QString decMsg = net->serverDecode(params.at(1)); |
210 | QRegExp welcomeRegExp("^\\[([^\\]]+)\\] " ); |
211 | if (welcomeRegExp.indexIn(decMsg) != -1) { |
212 | QString channelname = welcomeRegExp.cap(1); |
213 | decMsg = decMsg.mid(welcomeRegExp.matchedLength()); |
214 | CoreIrcChannel *chan = static_cast<CoreIrcChannel *>(net->ircChannel(channelname)); // we only have CoreIrcChannels in the core, so this cast is safe |
215 | if (chan && !chan->receivedWelcomeMsg()) { |
216 | chan->setReceivedWelcomeMsg(); |
217 | events << new MessageEvent(Message::Notice, net, decMsg, prefix, channelname, Message::None, e->timestamp()); |
218 | continue; |
219 | } |
220 | } |
221 | } |
222 | |
223 | if (prefix.isEmpty() || target == "AUTH" ) { |
224 | target = QString(); |
225 | } |
226 | else { |
227 | if (!target.isEmpty() && net->prefixes().contains(target.at(0))) |
228 | target = target.mid(1); |
229 | if (!net->isChannelName(target)) |
230 | target = nickFromMask(prefix); |
231 | } |
232 | |
233 | #ifdef HAVE_QCA2 |
234 | // Handle DH1080 key exchange |
235 | if (params[1].startsWith("DH1080_INIT" ) && !net->isChannelName(target)) { |
236 | events << new KeyEvent(EventManager::KeyEvent, net, prefix, target, KeyEvent::Init, params[1].mid(12)); |
237 | } else if (params[1].startsWith("DH1080_FINISH" ) && !net->isChannelName(target)) { |
238 | events << new KeyEvent(EventManager::KeyEvent, net, prefix, target, KeyEvent::Finish, params[1].mid(14)); |
239 | } else |
240 | #endif |
241 | events << new IrcEventRawMessage(EventManager::IrcEventRawNotice, net, params[1], prefix, target, e->timestamp()); |
242 | } |
243 | } |
244 | break; |
245 | |
246 | // the following events need only special casing for param decoding |
247 | case EventManager::IrcEventKick: |
248 | if (params.count() >= 3) { // we have a reason |
249 | decParams << net->serverDecode(params.at(0)) << net->serverDecode(params.at(1)); |
250 | decParams << net->channelDecode(decParams.first(), params.at(2)); // kick reason |
251 | } |
252 | break; |
253 | |
254 | case EventManager::IrcEventPart: |
255 | if (params.count() >= 2) { |
256 | QString channel = net->serverDecode(params.at(0)); |
257 | decParams << channel; |
258 | decParams << net->userDecode(nickFromMask(prefix), params.at(1)); |
259 | } |
260 | break; |
261 | |
262 | case EventManager::IrcEventQuit: |
263 | if (params.count() >= 1) { |
264 | decParams << net->userDecode(nickFromMask(prefix), params.at(0)); |
265 | } |
266 | break; |
267 | |
268 | case EventManager::IrcEventTopic: |
269 | if (params.count() >= 1) { |
270 | QString channel = net->serverDecode(params.at(0)); |
271 | decParams << channel; |
272 | decParams << (params.count() >= 2 ? net->channelDecode(channel, decrypt(net, channel, params.at(1), true)) : QString()); |
273 | } |
274 | break; |
275 | |
276 | case EventManager::IrcEventNumeric: |
277 | switch (num) { |
278 | case 301: /* RPL_AWAY */ |
279 | if (params.count() >= 2) { |
280 | QString nick = net->serverDecode(params.at(0)); |
281 | decParams << nick; |
282 | decParams << net->userDecode(nick, params.at(1)); |
283 | } |
284 | break; |
285 | |
286 | case 332: /* RPL_TOPIC */ |
287 | if (params.count() >= 2) { |
288 | QString channel = net->serverDecode(params.at(0)); |
289 | decParams << channel; |
290 | decParams << net->channelDecode(channel, decrypt(net, channel, params.at(1), true)); |
291 | } |
292 | break; |
293 | |
294 | case 333: /* Topic set by... */ |
295 | if (params.count() >= 3) { |
296 | QString channel = net->serverDecode(params.at(0)); |
297 | decParams << channel << net->serverDecode(params.at(1)); |
298 | decParams << net->channelDecode(channel, params.at(2)); |
299 | } |
300 | break; |
301 | } |
302 | |
303 | default: |
304 | break; |
305 | } |
306 | |
307 | if (defaultHandling && type != EventManager::Invalid) { |
308 | for (int i = decParams.count(); i < params.count(); i++) |
309 | decParams << net->serverDecode(params.at(i)); |
310 | |
311 | // We want to trim the last param just in case, except for PRIVMSG and NOTICE |
312 | // ... but those happen to be the only ones not using defaultHandling anyway |
313 | if (!decParams.isEmpty() && decParams.last().endsWith(' ')) |
314 | decParams.append(decParams.takeLast().trimmed()); |
315 | |
316 | IrcEvent *event; |
317 | if (type == EventManager::IrcEventNumeric) |
318 | event = new IrcEventNumeric(num, net, prefix, target); |
319 | else |
320 | event = new IrcEvent(type, net, prefix); |
321 | event->setParams(decParams); |
322 | event->setTimestamp(e->timestamp()); |
323 | events << event; |
324 | } |
325 | |
326 | foreach(Event *event, events) { |
327 | emit newEvent(event); |
328 | } |
329 | } |
330 | |