Fix file transfer under SSL
Introduces a big fat buffer :( Actually test the trasfers :) Takes QSslSocket causistic into account, for some reason QNAM refuses to mark as finished when the QSslSocket (through QIODevice) closes. It would be good to look into dropping the QBuffer, doing so with the test in place will help.
This commit is contained in:
parent
86b086e392
commit
ef98fb4587
6 changed files with 115 additions and 20 deletions
|
@ -37,9 +37,13 @@ DownloadJob::DownloadJob(const QHostAddress &address, const QVariantMap &transfe
|
||||||
: KJob()
|
: KJob()
|
||||||
, mAddress(address)
|
, mAddress(address)
|
||||||
, mPort(transferInfo["port"].toInt())
|
, mPort(transferInfo["port"].toInt())
|
||||||
, mSocket(new QSslSocket(this))
|
, mSocket(new QSslSocket)
|
||||||
|
, mBuffer(new QBuffer)
|
||||||
{
|
{
|
||||||
LanLinkProvider::configureSslSocket(mSocket.data(), transferInfo.value("deviceId").toString(), true);
|
LanLinkProvider::configureSslSocket(mSocket.data(), transferInfo.value("deviceId").toString(), true);
|
||||||
|
|
||||||
|
connect(mSocket.data(), SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketFailed(QAbstractSocket::SocketError)));
|
||||||
|
// connect(mSocket.data(), &QAbstractSocket::stateChanged, [](QAbstractSocket::SocketState state){ qDebug() << "statechange" << state; });
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadJob::~DownloadJob()
|
DownloadJob::~DownloadJob()
|
||||||
|
@ -50,23 +54,28 @@ DownloadJob::~DownloadJob()
|
||||||
void DownloadJob::start()
|
void DownloadJob::start()
|
||||||
{
|
{
|
||||||
//TODO: Timeout?
|
//TODO: Timeout?
|
||||||
connect(mSocket.data(), &QAbstractSocket::disconnected, this, &DownloadJob::emitResult);
|
|
||||||
connect(mSocket.data(), SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketFailed(QAbstractSocket::SocketError)));
|
|
||||||
// connect(mSocket.data(), &QAbstractSocket::stateChanged, [](QAbstractSocket::SocketState state){ qDebug() << "statechange" << state; });
|
|
||||||
|
|
||||||
// Cannot use read only, might be due to ssl handshake, getting QIODevice::ReadOnly error and no connection
|
// Cannot use read only, might be due to ssl handshake, getting QIODevice::ReadOnly error and no connection
|
||||||
mSocket->connectToHostEncrypted(mAddress.toString(), mPort, QIODevice::ReadWrite);
|
mSocket->connectToHostEncrypted(mAddress.toString(), mPort, QIODevice::ReadWrite);
|
||||||
|
|
||||||
|
bool b = mBuffer->open(QBuffer::ReadWrite);
|
||||||
|
Q_ASSERT(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DownloadJob::socketFailed(QAbstractSocket::SocketError error)
|
void DownloadJob::socketFailed(QAbstractSocket::SocketError error)
|
||||||
{
|
{
|
||||||
|
if (error != QAbstractSocket::RemoteHostClosedError) { //remote host closes when finishes
|
||||||
qWarning(KDECONNECT_CORE) << "error..." << mSocket->errorString();
|
qWarning(KDECONNECT_CORE) << "error..." << mSocket->errorString();
|
||||||
setError(error + 1);
|
setError(error + 1);
|
||||||
setErrorText(mSocket->errorString());
|
setErrorText(mSocket->errorString());
|
||||||
|
} else {
|
||||||
|
auto ba = mSocket->readAll();
|
||||||
|
mBuffer->write(ba);
|
||||||
|
mBuffer->seek(0);
|
||||||
|
}
|
||||||
emitResult();
|
emitResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<QIODevice> DownloadJob::getPayload()
|
QSharedPointer<QIODevice> DownloadJob::getPayload()
|
||||||
{
|
{
|
||||||
return mSocket.staticCast<QIODevice>();
|
return mBuffer.staticCast<QIODevice>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QSslSocket>
|
#include <QSslSocket>
|
||||||
|
#include <QBuffer>
|
||||||
#include <kdeconnectcore_export.h>
|
#include <kdeconnectcore_export.h>
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ private:
|
||||||
QHostAddress mAddress;
|
QHostAddress mAddress;
|
||||||
qint16 mPort;
|
qint16 mPort;
|
||||||
QSharedPointer<QSslSocket> mSocket;
|
QSharedPointer<QSslSocket> mSocket;
|
||||||
|
QSharedPointer<QBuffer> mBuffer;
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void socketFailed(QAbstractSocket::SocketError error);
|
void socketFailed(QAbstractSocket::SocketError error);
|
||||||
|
|
|
@ -27,7 +27,8 @@
|
||||||
|
|
||||||
#include "core_debug.h"
|
#include "core_debug.h"
|
||||||
|
|
||||||
UploadJob::UploadJob(const QSharedPointer<QIODevice>& source, const QString& deviceId): KJob()
|
UploadJob::UploadJob(const QSharedPointer<QIODevice>& source, const QString& deviceId)
|
||||||
|
: KJob()
|
||||||
{
|
{
|
||||||
// TODO: initialize in constructor
|
// TODO: initialize in constructor
|
||||||
mInput = source;
|
mInput = source;
|
||||||
|
@ -38,7 +39,7 @@ UploadJob::UploadJob(const QSharedPointer<QIODevice>& source, const QString& dev
|
||||||
// We will use this info if link is on ssl, to send encrypted payload
|
// We will use this info if link is on ssl, to send encrypted payload
|
||||||
this->mDeviceId = deviceId;
|
this->mDeviceId = deviceId;
|
||||||
|
|
||||||
connect(mInput.data(), SIGNAL(readyRead()), this, SLOT(readyRead()));
|
connect(mInput.data(), SIGNAL(readyRead()), this, SLOT(startUploading()));
|
||||||
connect(mInput.data(), SIGNAL(aboutToClose()), this, SLOT(aboutToClose()));
|
connect(mInput.data(), SIGNAL(aboutToClose()), this, SLOT(aboutToClose()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ void UploadJob::start()
|
||||||
if (mPort > 1764) { //No ports available?
|
if (mPort > 1764) { //No ports available?
|
||||||
qCWarning(KDECONNECT_CORE) << "Error opening a port in range 1739-1764 for file transfer";
|
qCWarning(KDECONNECT_CORE) << "Error opening a port in range 1739-1764 for file transfer";
|
||||||
mPort = 0;
|
mPort = 0;
|
||||||
|
emitResult();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,17 +70,19 @@ void UploadJob::newConnection()
|
||||||
disconnect(mServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
|
disconnect(mServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
|
||||||
|
|
||||||
mSocket = server->nextPendingConnection();
|
mSocket = server->nextPendingConnection();
|
||||||
connect(mSocket, SIGNAL(disconnected()), mSocket, SLOT(deleteLater()));
|
mSocket->setParent(this);
|
||||||
|
connect(mSocket, &QSslSocket::disconnected, this, &UploadJob::cleanup);
|
||||||
|
connect(mSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketFailed(QAbstractSocket::SocketError)));
|
||||||
|
connect(mSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrors(QList<QSslError>)));
|
||||||
|
connect(mSocket, &QSslSocket::encrypted, this, &UploadJob::startUploading);
|
||||||
|
// connect(mSocket, &QAbstractSocket::stateChanged, [](QAbstractSocket::SocketState state){ qDebug() << "statechange" << state; });
|
||||||
|
|
||||||
LanLinkProvider::configureSslSocket(mSocket, mDeviceId, true);
|
LanLinkProvider::configureSslSocket(mSocket, mDeviceId, true);
|
||||||
|
|
||||||
mSocket->startServerEncryption();
|
mSocket->startServerEncryption();
|
||||||
mSocket->waitForEncrypted();
|
|
||||||
|
|
||||||
readyRead();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UploadJob::readyRead()
|
void UploadJob::startUploading()
|
||||||
{
|
{
|
||||||
while ( mInput->bytesAvailable() > 0 )
|
while ( mInput->bytesAvailable() > 0 )
|
||||||
{
|
{
|
||||||
|
@ -93,14 +97,19 @@ void UploadJob::readyRead()
|
||||||
while ( mSocket->flush() );
|
while ( mSocket->flush() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mInput->close();
|
mInput->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UploadJob::aboutToClose()
|
void UploadJob::aboutToClose()
|
||||||
{
|
{
|
||||||
|
// qDebug() << "closing...";
|
||||||
mSocket->disconnectFromHost();
|
mSocket->disconnectFromHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UploadJob::cleanup()
|
||||||
|
{
|
||||||
mSocket->close();
|
mSocket->close();
|
||||||
|
// qDebug() << "closed!";
|
||||||
emitResult();
|
emitResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,3 +119,18 @@ QVariantMap UploadJob::transferInfo()
|
||||||
return {{"port", mPort}};
|
return {{"port", mPort}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UploadJob::socketFailed(QAbstractSocket::SocketError error)
|
||||||
|
{
|
||||||
|
qWarning() << "error uploading" << error;
|
||||||
|
setError(2);
|
||||||
|
emitResult();
|
||||||
|
mSocket->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UploadJob::sslErrors(const QList<QSslError>& errors)
|
||||||
|
{
|
||||||
|
qWarning() << "ssl errors" << errors;
|
||||||
|
setError(1);
|
||||||
|
emitResult();
|
||||||
|
mSocket->close();
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
#include <QSslSocket>
|
#include <QSslSocket>
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
class UploadJob
|
class KDECONNECTCORE_EXPORT UploadJob
|
||||||
: public KJob
|
: public KJob
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -48,9 +48,13 @@ private:
|
||||||
QString mDeviceId;
|
QString mDeviceId;
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void readyRead();
|
void startUploading();
|
||||||
void newConnection();
|
void newConnection();
|
||||||
void aboutToClose();
|
void aboutToClose();
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
void socketFailed(QAbstractSocket::SocketError);
|
||||||
|
void sslErrors(const QList<QSslError> &errors);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // UPLOADJOB_H
|
#endif // UPLOADJOB_H
|
||||||
|
|
|
@ -72,7 +72,7 @@ void FileTransferJob::doStart()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
startTransfer();
|
connect(mOrigin.data(), &QIODevice::readyRead, this, &FileTransferJob::startTransfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileTransferJob::startTransfer()
|
void FileTransferJob::startTransfer()
|
||||||
|
|
|
@ -19,6 +19,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QSocketNotifier>
|
#include <QSocketNotifier>
|
||||||
|
#include <backends/lan/downloadjob.h>
|
||||||
|
#include <kdeconnectconfig.h>
|
||||||
|
#include <backends/lan/uploadjob.h>
|
||||||
|
#include <core/filetransferjob.h>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QTest>
|
#include <QTest>
|
||||||
|
@ -83,6 +87,58 @@ class TestSendFile : public QObject
|
||||||
QCOMPARE(file.readAll(), content);
|
QCOMPARE(file.readAll(), content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testSslJobs()
|
||||||
|
{
|
||||||
|
const QString aFile = QFINDTESTDATA("sendfiletest.cpp");
|
||||||
|
const QString destFile = QDir::tempPath() + "/kdeconnect-test-sentfile";
|
||||||
|
QFile(destFile).remove();
|
||||||
|
|
||||||
|
const QString deviceId = KdeConnectConfig::instance()->deviceId()
|
||||||
|
, deviceName = "testdevice"
|
||||||
|
, deviceType = KdeConnectConfig::instance()->deviceType();
|
||||||
|
|
||||||
|
KdeConnectConfig* kcc = KdeConnectConfig::instance();
|
||||||
|
kcc->addTrustedDevice(deviceId, deviceName, deviceType);
|
||||||
|
kcc->setDeviceProperty(deviceId, QString("certificate"), QString::fromLatin1(kcc->certificate().toPem())); // Using same certificate from kcc, instead of generating
|
||||||
|
|
||||||
|
QSharedPointer<QFile> f(new QFile(aFile));
|
||||||
|
UploadJob* uj = new UploadJob(f, deviceId);
|
||||||
|
QSignalSpy spyUpload(uj, &KJob::result);
|
||||||
|
uj->start();
|
||||||
|
|
||||||
|
auto info = uj->transferInfo();
|
||||||
|
info.insert("deviceId", deviceId);
|
||||||
|
info.insert("size", aFile.size());
|
||||||
|
|
||||||
|
DownloadJob* dj = new DownloadJob(QHostAddress::LocalHost, info);
|
||||||
|
|
||||||
|
QVERIFY(dj->getPayload()->open(QIODevice::ReadOnly));
|
||||||
|
|
||||||
|
FileTransferJob* ft = new FileTransferJob(dj->getPayload(), uj->transferInfo()["size"].toInt(), QUrl::fromLocalFile(destFile));
|
||||||
|
|
||||||
|
QSignalSpy spyDownload(dj, &KJob::result);
|
||||||
|
QSignalSpy spyTransfer(ft, &KJob::result);
|
||||||
|
|
||||||
|
ft->start();
|
||||||
|
dj->start();
|
||||||
|
|
||||||
|
QVERIFY(spyTransfer.count() || spyTransfer.wait(10000000000000000000));
|
||||||
|
|
||||||
|
if (ft->error()) qWarning() << "fterror" << ft->errorString();
|
||||||
|
|
||||||
|
QCOMPARE(ft->error(), 0);
|
||||||
|
QCOMPARE(spyDownload.count(), 1);
|
||||||
|
QCOMPARE(spyUpload.count(), 1);
|
||||||
|
|
||||||
|
QFile resultFile(destFile), originFile(aFile);
|
||||||
|
QVERIFY(resultFile.open(QIODevice::ReadOnly));
|
||||||
|
QVERIFY(originFile.open(QIODevice::ReadOnly));
|
||||||
|
|
||||||
|
const QByteArray resultContents = resultFile.readAll(), originContents = originFile.readAll();
|
||||||
|
QCOMPARE(resultContents.size(), originContents.size());
|
||||||
|
QCOMPARE(resultFile.readAll(), originFile.readAll());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TestDaemon* mDaemon;
|
TestDaemon* mDaemon;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue