1 /***************************************************************************
2 * Copyright (C) 2005-2014 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>
24 #include "legacypeer.h"
27 /* version.inc is no longer used for this */
28 const uint protocolVersion = 10;
29 const uint coreNeedsProtocol = protocolVersion;
30 const uint clientNeedsProtocol = protocolVersion;
32 using namespace Protocol;
34 LegacyPeer::LegacyPeer(::AuthHandler *authHandler, QTcpSocket *socket, QObject *parent)
35 : RemotePeer(authHandler, socket, parent),
37 _useCompression(false)
39 _stream.setDevice(socket);
40 _stream.setVersion(QDataStream::Qt_4_2);
44 void LegacyPeer::setSignalProxy(::SignalProxy *proxy)
46 RemotePeer::setSignalProxy(proxy);
48 // FIXME only in compat mode
50 // enable compression now if requested - the initial handshake is uncompressed in the legacy protocol!
51 _useCompression = socket()->property("UseCompression").toBool();
53 qDebug() << "Using compression for peer:" << qPrintable(socket()->peerAddress().toString());
59 void LegacyPeer::onSocketDataAvailable()
62 while (readSocketData(item)) {
63 // if no sigproxy is set, we're in handshake mode and let the data be handled elsewhere
65 handleHandshakeMessage(item);
67 handlePackedFunc(item);
72 bool LegacyPeer::readSocketData(QVariant &item)
74 if (_blockSize == 0) {
75 if (socket()->bytesAvailable() < 4)
77 _stream >> _blockSize;
80 if (_blockSize > 1 << 22) {
81 close("Peer tried to send package larger than max package size!");
85 if (_blockSize == 0) {
86 close("Peer tried to send 0 byte package!");
90 if (socket()->bytesAvailable() < _blockSize) {
91 emit transferProgress(socket()->bytesAvailable(), _blockSize);
95 emit transferProgress(_blockSize, _blockSize);
99 if (_useCompression) {
103 int nbytes = rawItem.size();
105 const char *data = rawItem.constData();
106 if (nbytes < 4 || (data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 0)) {
107 close("Peer sent corrupted compressed data!");
112 rawItem = qUncompress(rawItem);
114 QDataStream itemStream(&rawItem, QIODevice::ReadOnly);
115 itemStream.setVersion(QDataStream::Qt_4_2);
122 if (!item.isValid()) {
123 close("Peer sent corrupt data: unable to load QVariant!");
131 void LegacyPeer::writeSocketData(const QVariant &item)
133 if (!socket()->isOpen()) {
134 qWarning() << Q_FUNC_INFO << "Can't write to a closed socket!";
139 QDataStream out(&block, QIODevice::WriteOnly);
140 out.setVersion(QDataStream::Qt_4_2);
142 if (_useCompression) {
144 QDataStream itemStream(&rawItem, QIODevice::WriteOnly);
145 itemStream.setVersion(QDataStream::Qt_4_2);
148 rawItem = qCompress(rawItem);
156 _stream << block; // also writes the length as part of the serialization format
160 /*** Handshake messages ***/
162 /* These messages are transmitted during handshake phase, which in case of the legacy protocol means they have
163 * a structure different from those being used after the handshake.
164 * Also, the legacy handshake does not fully match the redesigned one, so we'll have to do various mappings here.
167 void LegacyPeer::handleHandshakeMessage(const QVariant &msg)
169 QVariantMap m = msg.toMap();
171 QString msgType = m["MsgType"].toString();
172 if (msgType.isEmpty()) {
173 emit protocolError(tr("Invalid handshake message!"));
177 if (msgType == "ClientInit") {
178 // FIXME only in compat mode
179 uint ver = m["ProtocolVersion"].toUInt();
180 if (ver < coreNeedsProtocol) {
181 emit protocolVersionMismatch((int)ver, (int)coreNeedsProtocol);
185 #ifndef QT_NO_COMPRESS
186 // FIXME only in compat mode
187 if (m["UseCompression"].toBool()) {
188 socket()->setProperty("UseCompression", true);
191 handle(RegisterClient(m["ClientVersion"].toString(), m["UseSsl"].toBool()));
194 else if (msgType == "ClientInitReject") {
195 handle(ClientDenied(m["Error"].toString()));
198 else if (msgType == "ClientInitAck") {
199 // FIXME only in compat mode
200 uint ver = m["ProtocolVersion"].toUInt(); // actually an UInt
201 if (ver < clientNeedsProtocol) {
202 emit protocolVersionMismatch((int)ver, (int)clientNeedsProtocol);
205 #ifndef QT_NO_COMPRESS
206 if (m["SupportsCompression"].toBool())
207 socket()->setProperty("UseCompression", true);
210 handle(ClientRegistered(m["CoreFeatures"].toUInt(), m["Configured"].toBool(), m["StorageBackends"].toList(), m["SupportSsl"].toBool(), QDateTime()));
213 else if (msgType == "CoreSetupData") {
214 QVariantMap map = m["SetupData"].toMap();
215 handle(SetupData(map["AdminUser"].toString(), map["AdminPasswd"].toString(), map["Backend"].toString(), map["ConnectionProperties"].toMap()));
218 else if (msgType == "CoreSetupReject") {
219 handle(SetupFailed(m["Error"].toString()));
222 else if (msgType == "CoreSetupAck") {
226 else if (msgType == "ClientLogin") {
227 handle(Login(m["User"].toString(), m["Password"].toString()));
230 else if (msgType == "ClientLoginReject") {
231 handle(LoginFailed(m["Error"].toString()));
234 else if (msgType == "ClientLoginAck") {
235 handle(LoginSuccess());
238 else if (msgType == "SessionInit") {
239 QVariantMap map = m["SessionState"].toMap();
240 handle(SessionState(map["Identities"].toList(), map["BufferInfos"].toList(), map["NetworkIds"].toList()));
244 emit protocolError(tr("Unknown protocol message of type %1").arg(msgType));
249 void LegacyPeer::dispatch(const RegisterClient &msg) {
251 m["MsgType"] = "ClientInit";
252 m["ClientVersion"] = msg.clientVersion;
253 m["ClientDate"] = Quassel::buildInfo().buildDate;
255 // FIXME only in compat mode
256 m["ProtocolVersion"] = protocolVersion;
257 m["UseSsl"] = msg.sslSupported;
258 #ifndef QT_NO_COMPRESS
259 m["UseCompression"] = true;
261 m["UseCompression"] = false;
268 void LegacyPeer::dispatch(const ClientDenied &msg) {
270 m["MsgType"] = "ClientInitReject";
271 m["Error"] = msg.errorString;
277 void LegacyPeer::dispatch(const ClientRegistered &msg) {
279 m["MsgType"] = "ClientInitAck";
280 m["CoreFeatures"] = msg.coreFeatures;
281 m["StorageBackends"] = msg.backendInfo;
283 // FIXME only in compat mode
284 m["ProtocolVersion"] = protocolVersion;
285 m["SupportSsl"] = msg.sslSupported;
286 m["SupportsCompression"] = socket()->property("UseCompression").toBool(); // this property gets already set in the ClientInit handler
288 // This is only used for old v10 clients (pre-0.5)
289 int uptime = msg.coreStartTime.secsTo(QDateTime::currentDateTime().toUTC());
290 int updays = uptime / 86400; uptime %= 86400;
291 int uphours = uptime / 3600; uptime %= 3600;
292 int upmins = uptime / 60;
293 m["CoreInfo"] = tr("<b>Quassel Core Version %1</b><br>"
295 "Up %3d%4h%5m (since %6)").arg(Quassel::buildInfo().fancyVersionString)
296 .arg(Quassel::buildInfo().buildDate)
297 .arg(updays).arg(uphours, 2, 10, QChar('0')).arg(upmins, 2, 10, QChar('0')).arg(msg.coreStartTime.toString(Qt::TextDate));
299 m["LoginEnabled"] = m["Configured"] = msg.coreConfigured;
305 void LegacyPeer::dispatch(const SetupData &msg)
308 map["AdminUser"] = msg.adminUser;
309 map["AdminPasswd"] = msg.adminPassword;
310 map["Backend"] = msg.backend;
311 map["ConnectionProperties"] = msg.setupData;
314 m["MsgType"] = "CoreSetupData";
315 m["SetupData"] = map;
320 void LegacyPeer::dispatch(const SetupFailed &msg)
323 m["MsgType"] = "CoreSetupReject";
324 m["Error"] = msg.errorString;
330 void LegacyPeer::dispatch(const SetupDone &msg)
335 m["MsgType"] = "CoreSetupAck";
341 void LegacyPeer::dispatch(const Login &msg)
344 m["MsgType"] = "ClientLogin";
345 m["User"] = msg.user;
346 m["Password"] = msg.password;
352 void LegacyPeer::dispatch(const LoginFailed &msg)
355 m["MsgType"] = "ClientLoginReject";
356 m["Error"] = msg.errorString;
362 void LegacyPeer::dispatch(const LoginSuccess &msg)
367 m["MsgType"] = "ClientLoginAck";
373 void LegacyPeer::dispatch(const SessionState &msg)
376 m["MsgType"] = "SessionInit";
379 map["BufferInfos"] = msg.bufferInfos;
380 map["NetworkIds"] = msg.networkIds;
381 map["Identities"] = msg.identities;
382 m["SessionState"] = map;
388 /*** Standard messages ***/
390 void LegacyPeer::handlePackedFunc(const QVariant &packedFunc)
392 QVariantList params(packedFunc.toList());
394 if (params.isEmpty()) {
395 qWarning() << Q_FUNC_INFO << "Received incompatible data:" << packedFunc;
399 // TODO: make sure that this is a valid request type
400 RequestType requestType = (RequestType)params.takeFirst().value<int>();
401 switch (requestType) {
403 if (params.count() < 3) {
404 qWarning() << Q_FUNC_INFO << "Received invalid sync call:" << params;
407 QByteArray className = params.takeFirst().toByteArray();
408 QString objectName = params.takeFirst().toString();
409 QByteArray slotName = params.takeFirst().toByteArray();
410 handle(Protocol::SyncMessage(className, objectName, slotName, params));
414 if (params.empty()) {
415 qWarning() << Q_FUNC_INFO << "Received empty RPC call!";
418 QByteArray slotName = params.takeFirst().toByteArray();
419 handle(Protocol::RpcCall(slotName, params));
423 if (params.count() != 2) {
424 qWarning() << Q_FUNC_INFO << "Received invalid InitRequest:" << params;
427 QByteArray className = params[0].toByteArray();
428 QString objectName = params[1].toString();
429 handle(Protocol::InitRequest(className, objectName));
433 if (params.count() != 3) {
434 qWarning() << Q_FUNC_INFO << "Received invalid InitData:" << params;
437 QByteArray className = params[0].toByteArray();
438 QString objectName = params[1].toString();
439 QVariantMap initData = params[2].toMap();
441 // we need to special-case IrcUsersAndChannels here, since the format changed
442 if (className == "Network")
443 fromLegacyIrcUsersAndChannels(initData);
444 handle(Protocol::InitData(className, objectName, initData));
448 if (params.count() != 1) {
449 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
452 // The legacy protocol would only send a QTime, no QDateTime
453 // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
454 QDateTime dateTime = QDateTime::currentDateTime().toUTC();
455 dateTime.setTime(params[0].toTime());
456 handle(Protocol::HeartBeat(dateTime));
459 case HeartBeatReply: {
460 if (params.count() != 1) {
461 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
464 // The legacy protocol would only send a QTime, no QDateTime
465 // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
466 QDateTime dateTime = QDateTime::currentDateTime().toUTC();
467 dateTime.setTime(params[0].toTime());
468 handle(Protocol::HeartBeatReply(dateTime));
476 void LegacyPeer::dispatch(const Protocol::SyncMessage &msg)
478 dispatchPackedFunc(QVariantList() << (qint16)Sync << msg.className << msg.objectName << msg.slotName << msg.params);
482 void LegacyPeer::dispatch(const Protocol::RpcCall &msg)
484 dispatchPackedFunc(QVariantList() << (qint16)RpcCall << msg.slotName << msg.params);
488 void LegacyPeer::dispatch(const Protocol::InitRequest &msg)
490 dispatchPackedFunc(QVariantList() << (qint16)InitRequest << msg.className << msg.objectName);
494 void LegacyPeer::dispatch(const Protocol::InitData &msg)
496 // We need to special-case IrcUsersAndChannels, as the format changed
497 if (msg.className == "Network") {
498 QVariantMap initData = msg.initData;
499 toLegacyIrcUsersAndChannels(initData);
500 dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << initData);
503 dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << msg.initData);
507 void LegacyPeer::dispatch(const Protocol::HeartBeat &msg)
509 dispatchPackedFunc(QVariantList() << (qint16)HeartBeat << msg.timestamp.time());
513 void LegacyPeer::dispatch(const Protocol::HeartBeatReply &msg)
515 dispatchPackedFunc(QVariantList() << (qint16)HeartBeatReply << msg.timestamp.time());
519 void LegacyPeer::dispatchPackedFunc(const QVariantList &packedFunc)
521 writeSocketData(QVariant(packedFunc));
525 // Handle the changed format for Network's initData
526 // cf. Network::initIrcUsersAndChannels()
527 void LegacyPeer::fromLegacyIrcUsersAndChannels(QVariantMap &initData)
529 const QVariantMap &legacyMap = initData["IrcUsersAndChannels"].toMap();
532 QHash<QString, QVariantList> users;
533 foreach(const QVariant &v, legacyMap["users"].toMap().values()) {
534 const QVariantMap &map = v.toMap();
535 foreach(const QString &key, map.keys())
536 users[key] << map[key];
539 foreach(const QString &key, users.keys())
540 userMap[key] = users[key];
541 newMap["Users"] = userMap;
543 QHash<QString, QVariantList> channels;
544 foreach(const QVariant &v, legacyMap["channels"].toMap().values()) {
545 const QVariantMap &map = v.toMap();
546 foreach(const QString &key, map.keys())
547 channels[key] << map[key];
549 QVariantMap channelMap;
550 foreach(const QString &key, channels.keys())
551 channelMap[key] = channels[key];
552 newMap["Channels"] = channelMap;
554 initData["IrcUsersAndChannels"] = newMap;
558 void LegacyPeer::toLegacyIrcUsersAndChannels(QVariantMap &initData)
560 const QVariantMap &usersAndChannels = initData["IrcUsersAndChannels"].toMap();
561 QVariantMap legacyMap;
563 // toMap() and toList() are cheap, so no need to copy to a hash
566 const QVariantMap &users = usersAndChannels["Users"].toMap();
568 int size = users["nick"].toList().size(); // we know this key exists
569 for(int i = 0; i < size; i++) {
571 foreach(const QString &key, users.keys())
572 map[key] = users[key].toList().at(i);
573 QString hostmask = QString("%1!%2@%3").arg(map["nick"].toString(), map["user"].toString(), map["host"].toString());
574 userMap[hostmask.toLower()] = map;
576 legacyMap["users"] = userMap;
578 QVariantMap channelMap;
579 const QVariantMap &channels = usersAndChannels["Channels"].toMap();
581 size = channels["name"].toList().size();
582 for(int i = 0; i < size; i++) {
584 foreach(const QString &key, channels.keys())
585 map[key] = channels[key].toList().at(i);
586 channelMap[map["name"].toString().toLower()] = map;
588 legacyMap["channels"] = channelMap;
590 initData["IrcUsersAndChannels"] = legacyMap;