1 /***************************************************************************
2 * Copyright (C) 2005-2018 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 <QHostAddress>
22 #include <QDataStream>
25 #include "legacypeer.h"
26 #include "serializers/serializers.h"
28 /* version.inc is no longer used for this */
29 const uint protocolVersion = 10;
30 const uint coreNeedsProtocol = protocolVersion;
31 const uint clientNeedsProtocol = protocolVersion;
33 using namespace Protocol;
35 LegacyPeer::LegacyPeer(::AuthHandler *authHandler, QTcpSocket *socket, Compressor::CompressionLevel level, QObject *parent)
36 : RemotePeer(authHandler, socket, level, parent),
37 _useCompression(false)
43 void LegacyPeer::setSignalProxy(::SignalProxy *proxy)
45 RemotePeer::setSignalProxy(proxy);
47 // FIXME only in compat mode
49 // enable compression now if requested - the initial handshake is uncompressed in the legacy protocol!
50 _useCompression = socket()->property("UseCompression").toBool();
52 qDebug() << "Using compression for peer:" << qPrintable(socket()->peerAddress().toString());
58 void LegacyPeer::processMessage(const QByteArray &msg)
60 QDataStream stream(msg);
61 stream.setVersion(QDataStream::Qt_4_2);
64 if (_useCompression) {
66 if (!Serializers::deserialize(stream, rawItem)) {
67 close("Peer sent corrupt data: unable to load QVariant!");
71 int nbytes = rawItem.size();
73 const char *data = rawItem.constData();
74 if (nbytes < 4 || (data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 0)) {
75 close("Peer sent corrupted compressed data!");
80 rawItem = qUncompress(rawItem);
82 QDataStream itemStream(&rawItem, QIODevice::ReadOnly);
83 itemStream.setVersion(QDataStream::Qt_4_2);
84 if (!Serializers::deserialize(itemStream, item)) {
85 close("Peer sent corrupt data: unable to load QVariant!");
89 if (!Serializers::deserialize(stream, item)) {
90 close("Peer sent corrupt data: unable to load QVariant!");
95 if (stream.status() != QDataStream::Ok || !item.isValid()) {
96 close("Peer sent corrupt data: unable to load QVariant!");
100 // if no sigproxy is set, we're in handshake mode and let the data be handled elsewhere
102 handleHandshakeMessage(item);
104 handlePackedFunc(item);
108 void LegacyPeer::writeMessage(const QVariant &item)
111 QDataStream out(&block, QIODevice::WriteOnly);
112 out.setVersion(QDataStream::Qt_4_2);
114 if (_useCompression) {
116 QDataStream itemStream(&rawItem, QIODevice::WriteOnly);
117 itemStream.setVersion(QDataStream::Qt_4_2);
120 rawItem = qCompress(rawItem);
132 /*** Handshake messages ***/
134 /* These messages are transmitted during handshake phase, which in case of the legacy protocol means they have
135 * a structure different from those being used after the handshake.
136 * Also, the legacy handshake does not fully match the redesigned one, so we'll have to do various mappings here.
139 void LegacyPeer::handleHandshakeMessage(const QVariant &msg)
141 QVariantMap m = msg.toMap();
143 QString msgType = m["MsgType"].toString();
144 if (msgType.isEmpty()) {
145 emit protocolError(tr("Invalid handshake message!"));
149 if (msgType == "ClientInit") {
150 // FIXME only in compat mode
151 uint ver = m["ProtocolVersion"].toUInt();
152 if (ver < coreNeedsProtocol) {
153 emit protocolVersionMismatch((int)ver, (int)coreNeedsProtocol);
157 #ifndef QT_NO_COMPRESS
158 // FIXME only in compat mode
159 if (m["UseCompression"].toBool()) {
160 socket()->setProperty("UseCompression", true);
163 handle(RegisterClient(m["ClientVersion"].toString(), m["ClientDate"].toString(), m["UseSsl"].toBool()));
166 else if (msgType == "ClientInitReject") {
167 handle(ClientDenied(m["Error"].toString()));
170 else if (msgType == "ClientInitAck") {
171 // FIXME only in compat mode
172 uint ver = m["ProtocolVersion"].toUInt(); // actually an UInt
173 if (ver < clientNeedsProtocol) {
174 emit protocolVersionMismatch((int)ver, (int)clientNeedsProtocol);
177 #ifndef QT_NO_COMPRESS
178 if (m["SupportsCompression"].toBool())
179 socket()->setProperty("UseCompression", true);
182 handle(ClientRegistered(m["CoreFeatures"].toUInt(), m["Configured"].toBool(), m["StorageBackends"].toList(), m["SupportSsl"].toBool()));
185 else if (msgType == "CoreSetupData") {
186 QVariantMap map = m["SetupData"].toMap();
187 handle(SetupData(map["AdminUser"].toString(), map["AdminPasswd"].toString(), map["Backend"].toString(), map["ConnectionProperties"].toMap()));
190 else if (msgType == "CoreSetupReject") {
191 handle(SetupFailed(m["Error"].toString()));
194 else if (msgType == "CoreSetupAck") {
198 else if (msgType == "ClientLogin") {
199 handle(Login(m["User"].toString(), m["Password"].toString()));
202 else if (msgType == "ClientLoginReject") {
203 handle(LoginFailed(m["Error"].toString()));
206 else if (msgType == "ClientLoginAck") {
207 handle(LoginSuccess());
210 else if (msgType == "SessionInit") {
211 QVariantMap map = m["SessionState"].toMap();
212 handle(SessionState(map["Identities"].toList(), map["BufferInfos"].toList(), map["NetworkIds"].toList()));
216 emit protocolError(tr("Unknown protocol message of type %1").arg(msgType));
221 void LegacyPeer::dispatch(const RegisterClient &msg) {
223 m["MsgType"] = "ClientInit";
224 m["ClientVersion"] = msg.clientVersion;
225 m["ClientDate"] = msg.buildDate;
227 // FIXME only in compat mode
228 m["ProtocolVersion"] = protocolVersion;
229 m["UseSsl"] = msg.sslSupported;
230 #ifndef QT_NO_COMPRESS
231 m["UseCompression"] = true;
233 m["UseCompression"] = false;
240 void LegacyPeer::dispatch(const ClientDenied &msg) {
242 m["MsgType"] = "ClientInitReject";
243 m["Error"] = msg.errorString;
249 void LegacyPeer::dispatch(const ClientRegistered &msg) {
251 m["MsgType"] = "ClientInitAck";
252 m["CoreFeatures"] = msg.coreFeatures;
253 m["StorageBackends"] = msg.backendInfo;
255 // FIXME only in compat mode
256 m["ProtocolVersion"] = protocolVersion;
257 m["SupportSsl"] = msg.sslSupported;
258 m["SupportsCompression"] = socket()->property("UseCompression").toBool(); // this property gets already set in the ClientInit handler
260 // This is only used for display by really old v10 clients (pre-0.5), and we no longer set this
261 m["CoreInfo"] = QString();
263 m["LoginEnabled"] = m["Configured"] = msg.coreConfigured;
269 void LegacyPeer::dispatch(const SetupData &msg)
272 map["AdminUser"] = msg.adminUser;
273 map["AdminPasswd"] = msg.adminPassword;
274 map["Backend"] = msg.backend;
275 map["ConnectionProperties"] = msg.setupData;
278 m["MsgType"] = "CoreSetupData";
279 m["SetupData"] = map;
284 void LegacyPeer::dispatch(const SetupFailed &msg)
287 m["MsgType"] = "CoreSetupReject";
288 m["Error"] = msg.errorString;
294 void LegacyPeer::dispatch(const SetupDone &msg)
299 m["MsgType"] = "CoreSetupAck";
305 void LegacyPeer::dispatch(const Login &msg)
308 m["MsgType"] = "ClientLogin";
309 m["User"] = msg.user;
310 m["Password"] = msg.password;
316 void LegacyPeer::dispatch(const LoginFailed &msg)
319 m["MsgType"] = "ClientLoginReject";
320 m["Error"] = msg.errorString;
326 void LegacyPeer::dispatch(const LoginSuccess &msg)
331 m["MsgType"] = "ClientLoginAck";
337 void LegacyPeer::dispatch(const SessionState &msg)
340 m["MsgType"] = "SessionInit";
343 map["BufferInfos"] = msg.bufferInfos;
344 map["NetworkIds"] = msg.networkIds;
345 map["Identities"] = msg.identities;
346 m["SessionState"] = map;
352 /*** Standard messages ***/
354 void LegacyPeer::handlePackedFunc(const QVariant &packedFunc)
356 QVariantList params(packedFunc.toList());
358 if (params.isEmpty()) {
359 qWarning() << Q_FUNC_INFO << "Received incompatible data:" << packedFunc;
363 // TODO: make sure that this is a valid request type
364 RequestType requestType = (RequestType)params.takeFirst().value<int>();
365 switch (requestType) {
367 if (params.count() < 3) {
368 qWarning() << Q_FUNC_INFO << "Received invalid sync call:" << params;
371 QByteArray className = params.takeFirst().toByteArray();
372 QString objectName = params.takeFirst().toString();
373 QByteArray slotName = params.takeFirst().toByteArray();
374 handle(Protocol::SyncMessage(className, objectName, slotName, params));
378 if (params.empty()) {
379 qWarning() << Q_FUNC_INFO << "Received empty RPC call!";
382 QByteArray slotName = params.takeFirst().toByteArray();
383 handle(Protocol::RpcCall(slotName, params));
387 if (params.count() != 2) {
388 qWarning() << Q_FUNC_INFO << "Received invalid InitRequest:" << params;
391 QByteArray className = params[0].toByteArray();
392 QString objectName = params[1].toString();
393 handle(Protocol::InitRequest(className, objectName));
397 if (params.count() != 3) {
398 qWarning() << Q_FUNC_INFO << "Received invalid InitData:" << params;
401 QByteArray className = params[0].toByteArray();
402 QString objectName = params[1].toString();
403 QVariantMap initData = params[2].toMap();
405 // we need to special-case IrcUsersAndChannels here, since the format changed
406 if (className == "Network")
407 fromLegacyIrcUsersAndChannels(initData);
408 handle(Protocol::InitData(className, objectName, initData));
412 if (params.count() != 1) {
413 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
416 // The legacy protocol would only send a QTime, no QDateTime
417 // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
418 QDateTime dateTime = QDateTime::currentDateTime().toUTC();
419 dateTime.setTime(params[0].toTime());
420 handle(Protocol::HeartBeat(dateTime));
423 case HeartBeatReply: {
424 if (params.count() != 1) {
425 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
428 // The legacy protocol would only send a QTime, no QDateTime
429 // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
430 QDateTime dateTime = QDateTime::currentDateTime().toUTC();
431 dateTime.setTime(params[0].toTime());
432 handle(Protocol::HeartBeatReply(dateTime));
440 void LegacyPeer::dispatch(const Protocol::SyncMessage &msg)
442 dispatchPackedFunc(QVariantList() << (qint16)Sync << msg.className << msg.objectName << msg.slotName << msg.params);
446 void LegacyPeer::dispatch(const Protocol::RpcCall &msg)
448 dispatchPackedFunc(QVariantList() << (qint16)RpcCall << msg.slotName << msg.params);
452 void LegacyPeer::dispatch(const Protocol::InitRequest &msg)
454 dispatchPackedFunc(QVariantList() << (qint16)InitRequest << msg.className << msg.objectName);
458 void LegacyPeer::dispatch(const Protocol::InitData &msg)
460 // We need to special-case IrcUsersAndChannels, as the format changed
461 if (msg.className == "Network") {
462 QVariantMap initData = msg.initData;
463 toLegacyIrcUsersAndChannels(initData);
464 dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << initData);
467 dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << msg.initData);
471 void LegacyPeer::dispatch(const Protocol::HeartBeat &msg)
473 dispatchPackedFunc(QVariantList() << (qint16)HeartBeat << msg.timestamp.time());
477 void LegacyPeer::dispatch(const Protocol::HeartBeatReply &msg)
479 dispatchPackedFunc(QVariantList() << (qint16)HeartBeatReply << msg.timestamp.time());
483 void LegacyPeer::dispatchPackedFunc(const QVariantList &packedFunc)
485 writeMessage(QVariant(packedFunc));
489 // Handle the changed format for Network's initData
490 // cf. Network::initIrcUsersAndChannels()
491 void LegacyPeer::fromLegacyIrcUsersAndChannels(QVariantMap &initData)
493 const QVariantMap &legacyMap = initData["IrcUsersAndChannels"].toMap();
496 QHash<QString, QVariantList> users;
497 foreach(const QVariant &v, legacyMap["users"].toMap().values()) {
498 const QVariantMap &map = v.toMap();
499 foreach(const QString &key, map.keys())
500 users[key] << map[key];
503 foreach(const QString &key, users.keys())
504 userMap[key] = users[key];
505 newMap["Users"] = userMap;
507 QHash<QString, QVariantList> channels;
508 foreach(const QVariant &v, legacyMap["channels"].toMap().values()) {
509 const QVariantMap &map = v.toMap();
510 foreach(const QString &key, map.keys())
511 channels[key] << map[key];
513 QVariantMap channelMap;
514 foreach(const QString &key, channels.keys())
515 channelMap[key] = channels[key];
516 newMap["Channels"] = channelMap;
518 initData["IrcUsersAndChannels"] = newMap;
522 void LegacyPeer::toLegacyIrcUsersAndChannels(QVariantMap &initData)
524 const QVariantMap &usersAndChannels = initData["IrcUsersAndChannels"].toMap();
525 QVariantMap legacyMap;
527 // toMap() and toList() are cheap, so no need to copy to a hash
530 const QVariantMap &users = usersAndChannels["Users"].toMap();
532 int size = users["nick"].toList().size(); // we know this key exists
533 for(int i = 0; i < size; i++) {
535 foreach(const QString &key, users.keys())
536 map[key] = users[key].toList().at(i);
537 QString hostmask = QString("%1!%2@%3").arg(map["nick"].toString(), map["user"].toString(), map["host"].toString());
538 userMap[hostmask.toLower()] = map;
540 legacyMap["users"] = userMap;
542 QVariantMap channelMap;
543 const QVariantMap &channels = usersAndChannels["Channels"].toMap();
545 size = channels["name"].toList().size();
546 for(int i = 0; i < size; i++) {
548 foreach(const QString &key, channels.keys())
549 map[key] = channels[key].toList().at(i);
550 channelMap[map["name"].toString().toLower()] = map;
552 legacyMap["channels"] = channelMap;
554 initData["IrcUsersAndChannels"] = legacyMap;