Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant
#include "qqmldebugconnection_p.h"
#include "qqmldebugclient_p.h"
#include <private/qpacketprotocol_p.h>
#include <private/qpacket_p.h>
#include <private/qobject_p.h>
#include <QtCore/qeventloop.h>
#include <QtCore/qtimer.h>
#include <QtCore/QHash>
#include <QtCore/qdatastream.h>
#include <QtNetwork/qlocalserver.h>
#include <QtNetwork/qlocalsocket.h>
#include <QtNetwork/qtcpsocket.h>
QT_BEGIN_NAMESPACE
static const int protocolVersion = 1;
static const QString serverId = QLatin1String("QDeclarativeDebugServer");
static const QString clientId = QLatin1String("QDeclarativeDebugClient");
class QQmlDebugConnectionPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QQmlDebugConnection)
public:
QQmlDebugConnectionPrivate();
QPacketProtocol *protocol = nullptr;
QIODevice *device = nullptr;
QLocalServer *server = nullptr;
QEventLoop handshakeEventLoop;
QTimer handshakeTimer;
bool gotHello = false;
int currentDataStreamVersion = QDataStream::Qt_4_7;
int maximumDataStreamVersion = QDataStream::Qt_DefaultCompiledVersion;
QHash <QString, float> serverPlugins;
QHash<QString, QQmlDebugClient *> plugins;
QStringList removedPlugins;
void advertisePlugins();
void createProtocol();
void flush();
};
QQmlDebugConnectionPrivate::QQmlDebugConnectionPrivate()
{
handshakeTimer.setSingleShot(true);
handshakeTimer.setInterval(3000);
}
void QQmlDebugConnectionPrivate::advertisePlugins()
{
Q_Q(QQmlDebugConnection);
if (!q->isConnected())
return;
QPacket pack(currentDataStreamVersion);
pack << serverId << 1 << plugins.keys();
protocol->send(pack.data());
flush();
}
void QQmlDebugConnection::socketConnected()
{
Q_D(QQmlDebugConnection);
QPacket pack(d->currentDataStreamVersion);
pack << serverId << 0 << protocolVersion << d->plugins.keys() << d->maximumDataStreamVersion
<< true; // We accept multiple messages per packet
d->protocol->send(pack.data());
d->flush();
}
void QQmlDebugConnection::socketDisconnected()
{
Q_D(QQmlDebugConnection);
d->gotHello = false;
emit disconnected();
}
void QQmlDebugConnection::protocolReadyRead()
{
Q_D(QQmlDebugConnection);
if (!d->gotHello) {
QPacket pack(d->currentDataStreamVersion, d->protocol->read());
QString name;
pack >> name;
bool validHello = false;
if (name == clientId) {
int op = -1;
pack >> op;
if (op == 0) {
int version = -1;
pack >> version;
if (version == protocolVersion) {
QStringList pluginNames;
QList<float> pluginVersions;
pack >> pluginNames;
if (!pack.atEnd())
pack >> pluginVersions;
const int pluginNamesSize = pluginNames.size();
const int pluginVersionsSize = pluginVersions.size();
for (int i = 0; i < pluginNamesSize; ++i) {
float pluginVersion = 1.0;
if (i < pluginVersionsSize)
pluginVersion = pluginVersions.at(i);
d->serverPlugins.insert(pluginNames.at(i), pluginVersion);
}
pack >> d->currentDataStreamVersion;
validHello = true;
}
}
}
if (!validHello) {
qWarning("QQmlDebugConnection: Invalid hello message");
close();
return;
}
d->gotHello = true;
emit connected();
QHash<QString, QQmlDebugClient *>::Iterator iter = d->plugins.begin();
for (; iter != d->plugins.end(); ++iter) {
QQmlDebugClient::State newState = QQmlDebugClient::Unavailable;
if (d->serverPlugins.contains(iter.key()))
newState = QQmlDebugClient::Enabled;
iter.value()->stateChanged(newState);
}
d->handshakeTimer.stop();
d->handshakeEventLoop.quit();
}
while (d->protocol->packetsAvailable()) {
QPacket pack(d->currentDataStreamVersion, d->protocol->read());
QString name;
pack >> name;
if (name == clientId) {
int op = -1;
pack >> op;
if (op == 1) {
// Service Discovery
QHash<QString, float> oldServerPlugins = d->serverPlugins;
d->serverPlugins.clear();
QStringList pluginNames;
QList<float> pluginVersions;
pack >> pluginNames;
if (!pack.atEnd())
pack >> pluginVersions;
const int pluginNamesSize = pluginNames.size();
const int pluginVersionsSize = pluginVersions.size();
for (int i = 0; i < pluginNamesSize; ++i) {
float pluginVersion = 1.0;
if (i < pluginVersionsSize)
pluginVersion = pluginVersions.at(i);
d->serverPlugins.insert(pluginNames.at(i), pluginVersion);
}
QHash<QString, QQmlDebugClient *>::Iterator iter = d->plugins.begin();
for (; iter != d->plugins.end(); ++iter) {
const QString &pluginName = iter.key();
QQmlDebugClient::State newState = QQmlDebugClient::Unavailable;
if (d->serverPlugins.contains(pluginName))
newState = QQmlDebugClient::Enabled;
if (oldServerPlugins.contains(pluginName)
!= d->serverPlugins.contains(pluginName)) {
iter.value()->stateChanged(newState);
}
}
} else {
qWarning() << "QQmlDebugConnection: Unknown control message id" << op;
}
} else {
QHash<QString, QQmlDebugClient *>::Iterator iter = d->plugins.find(name);
if (iter == d->plugins.end()) {
// We can get more messages for plugins we have removed because it takes time to
// send the advertisement message but the removal is instant locally.
if (!d->removedPlugins.contains(name)) {
qWarning() << "QQmlDebugConnection: Message received for missing plugin"
<< name;
}
} else {
QQmlDebugClient *client = *iter;
QByteArray message;
while (!pack.atEnd()) {
pack >> message;
client->messageReceived(message);
}
}
}
}
}
void QQmlDebugConnection::handshakeTimeout()
{
Q_D(QQmlDebugConnection);
if (!d->gotHello) {
qWarning() << "QQmlDebugConnection: Did not get handshake answer in time";
d->handshakeEventLoop.quit();
}
}
QQmlDebugConnection::QQmlDebugConnection(QObject *parent) :
QObject(*(new QQmlDebugConnectionPrivate), parent)
{
Q_D(QQmlDebugConnection);
connect(&d->handshakeTimer, &QTimer::timeout, this, &QQmlDebugConnection::handshakeTimeout);
}
QQmlDebugConnection::~QQmlDebugConnection()
{
Q_D(QQmlDebugConnection);
QHash<QString, QQmlDebugClient*>::iterator iter = d->plugins.begin();
for (; iter != d->plugins.end(); ++iter)
emit iter.value()->stateChanged(QQmlDebugClient::NotConnected);
}
int QQmlDebugConnection::currentDataStreamVersion() const
{
Q_D(const QQmlDebugConnection);
return d->currentDataStreamVersion;
}
void QQmlDebugConnection::setMaximumDataStreamVersion(int maximumVersion)
{
Q_D(QQmlDebugConnection);
d->maximumDataStreamVersion = maximumVersion;
}
bool QQmlDebugConnection::isConnected() const
{
Q_D(const QQmlDebugConnection);
return d->gotHello;
}
bool QQmlDebugConnection::isConnecting() const
{
Q_D(const QQmlDebugConnection);
return !d->gotHello && d->device;
}
void QQmlDebugConnection::close()
{
Q_D(QQmlDebugConnection);
if (d->gotHello) {
d->gotHello = false;
d->device->close();
QHash<QString, QQmlDebugClient*>::iterator iter = d->plugins.begin();
for (; iter != d->plugins.end(); ++iter)
emit iter.value()->stateChanged(QQmlDebugClient::NotConnected);
}
if (d->device) {
d->device->deleteLater();
d->device = nullptr;
}
}
bool QQmlDebugConnection::waitForConnected(int msecs)
{
Q_D(QQmlDebugConnection);
auto socket = qobject_cast<QAbstractSocket*>(d->device);
if (!socket) {
if (!d->server || (!d->server->hasPendingConnections() &&
!d->server->waitForNewConnection(msecs)))
return false;
} else if (!socket->waitForConnected(msecs)) {
return false;
}
// wait for handshake
d->handshakeTimer.start();
d->handshakeEventLoop.exec();
return d->gotHello;
}
QQmlDebugClient *QQmlDebugConnection::client(const QString &name) const
{
Q_D(const QQmlDebugConnection);
return d->plugins.value(name, nullptr);
}
bool QQmlDebugConnection::addClient(const QString &name, QQmlDebugClient *client)
{
Q_D(QQmlDebugConnection);
if (d->plugins.contains(name))
return false;
d->removedPlugins.removeAll(name);
d->plugins.insert(name, client);
d->advertisePlugins();
return true;
}
bool QQmlDebugConnection::removeClient(const QString &name)
{
Q_D(QQmlDebugConnection);
if (!d->plugins.contains(name))
return false;
d->plugins.remove(name);
d->removedPlugins.append(name);
d->advertisePlugins();
return true;
}
float QQmlDebugConnection::serviceVersion(const QString &serviceName) const
{
Q_D(const QQmlDebugConnection);
return d->serverPlugins.value(serviceName, -1);
}
bool QQmlDebugConnection::sendMessage(const QString &name, const QByteArray &message)
{
Q_D(QQmlDebugConnection);
if (!isConnected() || !d->serverPlugins.contains(name))
return false;
QPacket pack(d->currentDataStreamVersion);
pack << name << message;
d->protocol->send(pack.data());
d->flush();
return true;
}
void QQmlDebugConnectionPrivate::flush()
{
if (auto socket = qobject_cast<QAbstractSocket *>(device))
socket->flush();
else if (auto socket = qobject_cast<QLocalSocket *>(device))
socket->flush();
}
void QQmlDebugConnection::connectToHost(const QString &hostName, quint16 port)
{
Q_D(QQmlDebugConnection);
if (d->gotHello)
close();
auto socket = new QTcpSocket(this);
d->device = socket;
d->createProtocol();
connect(socket, &QAbstractSocket::disconnected, this, &QQmlDebugConnection::socketDisconnected);
connect(socket, &QAbstractSocket::connected, this, &QQmlDebugConnection::socketConnected);
connect(socket, static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(
&QAbstractSocket::errorOccurred), this, &QQmlDebugConnection::socketError);
connect(socket, &QAbstractSocket::stateChanged, this, &QQmlDebugConnection::socketStateChanged);
socket->connectToHost(hostName, port);
}
void QQmlDebugConnection::startLocalServer(const QString &fileName)
{
Q_D(QQmlDebugConnection);
if (d->gotHello)
close();
if (d->server)
d->server->deleteLater();
d->server = new QLocalServer(this);
// QueuedConnection so that waitForNewConnection() returns true.
connect(d->server, &QLocalServer::newConnection,
this, &QQmlDebugConnection::newConnection, Qt::QueuedConnection);
d->server->listen(fileName);
}
class LocalSocketSignalTranslator : public QObject
{
Q_OBJECT
public:
LocalSocketSignalTranslator(QLocalSocket *parent) : QObject(parent)
{
connect(parent, &QLocalSocket::stateChanged,
this, &LocalSocketSignalTranslator::onStateChanged);
connect(parent, &QLocalSocket::errorOccurred, this, &LocalSocketSignalTranslator::onError);
}
void onError(QLocalSocket::LocalSocketError error)
{
emit socketError(static_cast<QAbstractSocket::SocketError>(error));
}
void onStateChanged(QLocalSocket::LocalSocketState state)
{
emit socketStateChanged(static_cast<QAbstractSocket::SocketState>(state));
}
signals:
void socketError(QAbstractSocket::SocketError);
void socketStateChanged(QAbstractSocket::SocketState);
};
void QQmlDebugConnection::newConnection()
{
Q_D(QQmlDebugConnection);
delete d->device;
QLocalSocket *socket = d->server->nextPendingConnection();
d->server->close();
d->device = socket;
d->createProtocol();
connect(socket, &QLocalSocket::disconnected, this, &QQmlDebugConnection::socketDisconnected);
auto translator = new LocalSocketSignalTranslator(socket);
connect(translator, &LocalSocketSignalTranslator::socketError,
this, &QQmlDebugConnection::socketError);
connect(translator, &LocalSocketSignalTranslator::socketStateChanged,
this, &QQmlDebugConnection::socketStateChanged);
socketConnected();
}
void QQmlDebugConnectionPrivate::createProtocol()
{
Q_Q(QQmlDebugConnection);
delete protocol;
protocol = new QPacketProtocol(device, q);
QObject::connect(protocol, &QPacketProtocol::readyRead,
q, &QQmlDebugConnection::protocolReadyRead);
}
QT_END_NAMESPACE
#include <qqmldebugconnection.moc>
#include "moc_qqmldebugconnection_p.cpp"