Fix bug in qsslsocket peek()
Calling peek() for qsslsocket caused socket data to be copied into qiodevices buffer and therefore make it unaccessible in qsslsocket. Cherry picked form 4.8-branch & modified to Qt5 API changes (int -> qintptr) Original commits: commit 621f18955082fc73471e75d1f8c35c2dcd4befeb Author: Shane Kearns <ext-shane.2.kearns@nokia.com> commit 68b1d5c17aa38d5921bdade2b0e0cb67c6c90513 Author: Kalle Viironen <kalle.viironen@digia.com> Task-number: QTBUG-18498 Change-Id: I6be4b19baec2f3197537f5e7b61432040ec84ad2 Reviewed-by: Shane Kearns <shane.kearns@accenture.com> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
d5f157c165
commit
83c637aa94
@ -110,6 +110,11 @@ public:
|
||||
first += r;
|
||||
return r;
|
||||
}
|
||||
int peek(char* target, int size) {
|
||||
int r = qMin(size, len);
|
||||
memcpy(target, first, r);
|
||||
return r;
|
||||
}
|
||||
char* reserve(int size) {
|
||||
makeSpace(size + len, freeSpaceAtEnd);
|
||||
char* writePtr = first + len;
|
||||
|
@ -2329,6 +2329,57 @@ bool QSslSocketPrivate::verifyErrorsHaveBeenIgnored()
|
||||
return !doEmitSslError;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
qint64 QSslSocketPrivate::peek(char *data, qint64 maxSize)
|
||||
{
|
||||
if (mode == QSslSocket::UnencryptedMode && !autoStartHandshake) {
|
||||
//unencrypted mode - do not use QIODevice::peek, as it reads ahead data from the plain socket
|
||||
//peek at data already in the QIODevice buffer (from a previous read)
|
||||
qint64 r = buffer.peek(data, maxSize);
|
||||
if (r == maxSize)
|
||||
return r;
|
||||
data += r;
|
||||
//peek at data in the plain socket
|
||||
if (plainSocket) {
|
||||
qint64 r2 = plainSocket->peek(data, maxSize - r);
|
||||
if (r2 < 0)
|
||||
return (r > 0 ? r : r2);
|
||||
return r + r2;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
//encrypted mode - the socket engine will read and decrypt data into the QIODevice buffer
|
||||
return QTcpSocketPrivate::peek(data, maxSize);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
QByteArray QSslSocketPrivate::peek(qint64 maxSize)
|
||||
{
|
||||
if (mode == QSslSocket::UnencryptedMode && !autoStartHandshake) {
|
||||
//unencrypted mode - do not use QIODevice::peek, as it reads ahead data from the plain socket
|
||||
//peek at data already in the QIODevice buffer (from a previous read)
|
||||
QByteArray ret;
|
||||
ret.reserve(maxSize);
|
||||
ret.resize(buffer.peek(ret.data(), maxSize));
|
||||
if (ret.length() == maxSize)
|
||||
return ret;
|
||||
//peek at data in the plain socket
|
||||
if (plainSocket)
|
||||
return ret + plainSocket->peek(maxSize - ret.length());
|
||||
else
|
||||
return QByteArray();
|
||||
} else {
|
||||
//encrypted mode - the socket engine will read and decrypt data into the QIODevice buffer
|
||||
return QTcpSocketPrivate::peek(maxSize);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
|
@ -166,6 +166,9 @@ public:
|
||||
virtual void _q_caRootLoaded(QSslCertificate,QSslCertificate) = 0;
|
||||
#endif
|
||||
|
||||
virtual qint64 peek(char *data, qint64 maxSize);
|
||||
virtual QByteArray peek(qint64 maxSize);
|
||||
|
||||
// Platform specific functions
|
||||
virtual void startClientEncryption() = 0;
|
||||
virtual void startServerEncryption() = 0;
|
||||
|
@ -189,6 +189,8 @@ private slots:
|
||||
void encryptWithoutConnecting();
|
||||
void resume_data();
|
||||
void resume();
|
||||
void qtbug18498_peek();
|
||||
void qtbug18498_peek2();
|
||||
void setEmptyDefaultConfiguration(); // this test should be last
|
||||
|
||||
static void exitLoop()
|
||||
@ -2198,6 +2200,261 @@ void tst_QSslSocket::resume()
|
||||
}
|
||||
}
|
||||
|
||||
class WebSocket : public QSslSocket
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WebSocket(qintptr socketDescriptor,
|
||||
const QString &keyFile = SRCDIR "certs/fluke.key",
|
||||
const QString &certFile = SRCDIR "certs/fluke.cert");
|
||||
|
||||
protected slots:
|
||||
void onReadyReadFirstBytes(void);
|
||||
|
||||
private:
|
||||
void _startServerEncryption(void);
|
||||
|
||||
QString m_keyFile;
|
||||
QString m_certFile;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(WebSocket)
|
||||
};
|
||||
|
||||
WebSocket::WebSocket (qintptr socketDescriptor, const QString &keyFile, const QString &certFile)
|
||||
: m_keyFile(keyFile),
|
||||
m_certFile(certFile)
|
||||
{
|
||||
QVERIFY(setSocketDescriptor(socketDescriptor, QAbstractSocket::ConnectedState, QIODevice::ReadWrite | QIODevice::Unbuffered));
|
||||
connect (this, SIGNAL(readyRead()), this, SLOT(onReadyReadFirstBytes()));
|
||||
}
|
||||
|
||||
void WebSocket::_startServerEncryption (void)
|
||||
{
|
||||
QFile file(m_keyFile);
|
||||
QVERIFY(file.open(QIODevice::ReadOnly));
|
||||
QSslKey key(file.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
|
||||
QVERIFY(!key.isNull());
|
||||
setPrivateKey(key);
|
||||
|
||||
QList<QSslCertificate> localCert = QSslCertificate::fromPath(m_certFile);
|
||||
QVERIFY(!localCert.isEmpty());
|
||||
QVERIFY(localCert.first().handle());
|
||||
setLocalCertificate(localCert.first());
|
||||
|
||||
QVERIFY(!peerAddress().isNull());
|
||||
QVERIFY(peerPort() != 0);
|
||||
QVERIFY(!localAddress().isNull());
|
||||
QVERIFY(localPort() != 0);
|
||||
|
||||
setProtocol(QSsl::AnyProtocol);
|
||||
setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
ignoreSslErrors();
|
||||
startServerEncryption();
|
||||
}
|
||||
|
||||
void WebSocket::onReadyReadFirstBytes (void)
|
||||
{
|
||||
peek(1);
|
||||
disconnect(this,SIGNAL(readyRead()), this, SLOT(onReadyReadFirstBytes()));
|
||||
_startServerEncryption();
|
||||
}
|
||||
|
||||
class SslServer4 : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SslServer4() : socket(0) {}
|
||||
WebSocket *socket;
|
||||
|
||||
protected:
|
||||
void incomingConnection(qintptr socketDescriptor)
|
||||
{
|
||||
socket = new WebSocket(socketDescriptor);
|
||||
}
|
||||
};
|
||||
|
||||
void tst_QSslSocket::qtbug18498_peek()
|
||||
{
|
||||
QFETCH_GLOBAL(bool, setProxy);
|
||||
if (setProxy)
|
||||
return;
|
||||
|
||||
SslServer4 server;
|
||||
QSslSocket *client = new QSslSocket(this);
|
||||
|
||||
QVERIFY(server.listen(QHostAddress::LocalHost));
|
||||
client->connectToHost("127.0.0.1", server.serverPort());
|
||||
QVERIFY(client->waitForConnected(5000));
|
||||
QVERIFY(server.waitForNewConnection(1000));
|
||||
client->setObjectName("client");
|
||||
client->ignoreSslErrors();
|
||||
|
||||
connect(client, SIGNAL(encrypted()), this, SLOT(exitLoop()));
|
||||
connect(client, SIGNAL(disconnected()), this, SLOT(exitLoop()));
|
||||
|
||||
client->startClientEncryption();
|
||||
WebSocket *serversocket = server.socket;
|
||||
QVERIFY(serversocket);
|
||||
serversocket->setObjectName("server");
|
||||
|
||||
enterLoop(1);
|
||||
QVERIFY(!timeout());
|
||||
QVERIFY(serversocket->isEncrypted());
|
||||
QVERIFY(client->isEncrypted());
|
||||
|
||||
QByteArray data("abc123");
|
||||
client->write(data.data());
|
||||
|
||||
connect(serversocket, SIGNAL(readyRead()), this, SLOT(exitLoop()));
|
||||
enterLoop(1);
|
||||
QVERIFY(!timeout());
|
||||
|
||||
QByteArray peek1_data;
|
||||
peek1_data.reserve(data.size());
|
||||
QByteArray peek2_data;
|
||||
QByteArray read_data;
|
||||
|
||||
int lngth = serversocket->peek(peek1_data.data(), 10);
|
||||
peek1_data.resize(lngth);
|
||||
|
||||
peek2_data = serversocket->peek(10);
|
||||
read_data = serversocket->readAll();
|
||||
|
||||
QCOMPARE(peek1_data, data);
|
||||
QCOMPARE(peek2_data, data);
|
||||
QCOMPARE(read_data, data);
|
||||
}
|
||||
|
||||
class SslServer5 : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SslServer5() : socket(0) {}
|
||||
QSslSocket *socket;
|
||||
|
||||
protected:
|
||||
void incomingConnection(qintptr socketDescriptor)
|
||||
{
|
||||
socket = new QSslSocket;
|
||||
socket->setSocketDescriptor(socketDescriptor);
|
||||
}
|
||||
};
|
||||
|
||||
void tst_QSslSocket::qtbug18498_peek2()
|
||||
{
|
||||
QFETCH_GLOBAL(bool, setProxy);
|
||||
if (setProxy)
|
||||
return;
|
||||
|
||||
SslServer5 listener;
|
||||
QVERIFY(listener.listen(QHostAddress::Any));
|
||||
QScopedPointer<QSslSocket> client(new QSslSocket);
|
||||
client->connectToHost(QHostAddress::LocalHost, listener.serverPort());
|
||||
QVERIFY(client->waitForConnected(5000));
|
||||
QVERIFY(listener.waitForNewConnection(1000));
|
||||
|
||||
QScopedPointer<QSslSocket> server(listener.socket);
|
||||
|
||||
QVERIFY(server->write("HELLO\r\n", 7));
|
||||
QElapsedTimer stopwatch;
|
||||
stopwatch.start();
|
||||
while (client->bytesAvailable() < 7 && stopwatch.elapsed() < 5000)
|
||||
QTest::qWait(100);
|
||||
char c;
|
||||
QVERIFY(client->peek(&c,1) == 1);
|
||||
QCOMPARE(c, 'H');
|
||||
QVERIFY(client->read(&c,1) == 1);
|
||||
QCOMPARE(c, 'H');
|
||||
QByteArray b = client->peek(2);
|
||||
QCOMPARE(b, QByteArray("EL"));
|
||||
char a[3];
|
||||
QVERIFY(client->peek(a, 2) == 2);
|
||||
QCOMPARE(a[0], 'E');
|
||||
QCOMPARE(a[1], 'L');
|
||||
QCOMPARE(client->readAll(), QByteArray("ELLO\r\n"));
|
||||
|
||||
//check data split between QIODevice and plain socket buffers.
|
||||
QByteArray bigblock;
|
||||
bigblock.fill('#', QIODEVICE_BUFFERSIZE + 1024);
|
||||
QVERIFY(client->write(QByteArray("head")));
|
||||
QVERIFY(client->write(bigblock));
|
||||
while (server->bytesAvailable() < bigblock.length() + 4 && stopwatch.elapsed() < 5000)
|
||||
QTest::qWait(100);
|
||||
QCOMPARE(server->read(4), QByteArray("head"));
|
||||
QCOMPARE(server->peek(bigblock.length()), bigblock);
|
||||
b.reserve(bigblock.length());
|
||||
b.resize(server->peek(b.data(), bigblock.length()));
|
||||
QCOMPARE(b, bigblock);
|
||||
|
||||
//check oversized peek
|
||||
QCOMPARE(server->peek(bigblock.length() * 3), bigblock);
|
||||
b.reserve(bigblock.length() * 3);
|
||||
b.resize(server->peek(b.data(), bigblock.length() * 3));
|
||||
QCOMPARE(b, bigblock);
|
||||
|
||||
QCOMPARE(server->readAll(), bigblock);
|
||||
|
||||
QVERIFY(client->write("STARTTLS\r\n"));
|
||||
stopwatch.start();
|
||||
// ### Qt5 use QTRY_VERIFY
|
||||
while (server->bytesAvailable() < 10 && stopwatch.elapsed() < 5000)
|
||||
QTest::qWait(100);
|
||||
QVERIFY(server->peek(&c,1) == 1);
|
||||
QCOMPARE(c, 'S');
|
||||
b = server->peek(3);
|
||||
QCOMPARE(b, QByteArray("STA"));
|
||||
QCOMPARE(server->read(5), QByteArray("START"));
|
||||
QVERIFY(server->peek(a, 3) == 3);
|
||||
QCOMPARE(a[0], 'T');
|
||||
QCOMPARE(a[1], 'L');
|
||||
QCOMPARE(a[2], 'S');
|
||||
QCOMPARE(server->readAll(), QByteArray("TLS\r\n"));
|
||||
|
||||
QFile file(SRCDIR "certs/fluke.key");
|
||||
QVERIFY(file.open(QIODevice::ReadOnly));
|
||||
QSslKey key(file.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
|
||||
QVERIFY(!key.isNull());
|
||||
server->setPrivateKey(key);
|
||||
|
||||
QList<QSslCertificate> localCert = QSslCertificate::fromPath(SRCDIR "certs/fluke.cert");
|
||||
QVERIFY(!localCert.isEmpty());
|
||||
QVERIFY(localCert.first().handle());
|
||||
server->setLocalCertificate(localCert.first());
|
||||
|
||||
server->setProtocol(QSsl::AnyProtocol);
|
||||
server->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
|
||||
server->ignoreSslErrors();
|
||||
client->ignoreSslErrors();
|
||||
|
||||
server->startServerEncryption();
|
||||
client->startClientEncryption();
|
||||
|
||||
QVERIFY(server->write("hello\r\n", 7));
|
||||
stopwatch.start();
|
||||
while (client->bytesAvailable() < 7 && stopwatch.elapsed() < 5000)
|
||||
QTest::qWait(100);
|
||||
QVERIFY(server->mode() == QSslSocket::SslServerMode && client->mode() == QSslSocket::SslClientMode);
|
||||
QVERIFY(client->peek(&c,1) == 1);
|
||||
QCOMPARE(c, 'h');
|
||||
QVERIFY(client->read(&c,1) == 1);
|
||||
QCOMPARE(c, 'h');
|
||||
b = client->peek(2);
|
||||
QCOMPARE(b, QByteArray("el"));
|
||||
QCOMPARE(client->readAll(), QByteArray("ello\r\n"));
|
||||
|
||||
QVERIFY(client->write("goodbye\r\n"));
|
||||
stopwatch.start();
|
||||
while (server->bytesAvailable() < 9 && stopwatch.elapsed() < 5000)
|
||||
QTest::qWait(100);
|
||||
QVERIFY(server->peek(&c,1) == 1);
|
||||
QCOMPARE(c, 'g');
|
||||
QCOMPARE(server->readAll(), QByteArray("goodbye\r\n"));
|
||||
client->disconnectFromHost();
|
||||
QVERIFY(client->waitForDisconnected(5000));
|
||||
}
|
||||
|
||||
void tst_QSslSocket::setEmptyDefaultConfiguration() // this test should be last, as it has some side effects
|
||||
{
|
||||
// used to produce a crash in QSslConfigurationPrivate::deepCopyDefaultConfiguration, QTBUG-13265
|
||||
|
Loading…
Reference in New Issue
Block a user