summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKalle Viironen <kalle.viironen@digia.com>2012-04-17 16:43:40 +0300
committerQt by Nokia <qt-info@nokia.com>2012-04-23 09:45:24 +0200
commit83c637aa942882880a73c7c4972c6c53332e0464 (patch)
treeb261ed1d40f0ea5baa39dd77c4a507c56475c9aa
parentd5f157c16548770374489a3c566df0e9fc19cc75 (diff)
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>
-rw-r--r--src/corelib/io/qiodevice_p.h5
-rw-r--r--src/network/ssl/qsslsocket.cpp51
-rw-r--r--src/network/ssl/qsslsocket_p.h3
-rw-r--r--tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp257
4 files changed, 316 insertions, 0 deletions
diff --git a/src/corelib/io/qiodevice_p.h b/src/corelib/io/qiodevice_p.h
index a510f5390c..4819ec11a0 100644
--- a/src/corelib/io/qiodevice_p.h
+++ b/src/corelib/io/qiodevice_p.h
@@ -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;
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index 6338cbbe6f..27c0123fbd 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -2332,6 +2332,57 @@ bool QSslSocketPrivate::verifyErrorsHaveBeenIgnored()
/*!
\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
+*/
QList<QByteArray> QSslSocketPrivate::unixRootCertDirectories()
{
return QList<QByteArray>() << "/etc/ssl/certs/" // (K)ubuntu, OpenSUSE, Mandriva, MeeGo ...
diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h
index ff51628511..3b97b5da6d 100644
--- a/src/network/ssl/qsslsocket_p.h
+++ b/src/network/ssl/qsslsocket_p.h
@@ -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;
diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp
index 23e87b7f3b..333102030a 100644
--- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp
+++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp
@@ -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