QAbstractSocket / QSslSocket: add API to pause and resume
pause and resume is currently only supported upon emitting the QSslSocket::sslErrors() signal. The API was added in QAbstractSocket to also support QAbstractSocket::proxyAuthenticationRequired() in the future. This is the first patch to support that feature on the socket level, another patch will follow to support sslErrors() and authenticationRequired() in QNetworkAccessManager / QNetworkReply. Task-number: QTBUG-19032 Change-Id: Ide2918268590ab9a01454ab26cb7fdca3dc840ab Reviewed-by: Shane Kearns <ext-shane.2.kearns@nokia.com>
This commit is contained in:
parent
0da4451b78
commit
07662f93ac
@ -425,6 +425,19 @@
|
||||
+ ReuseAddressHint), and on Windows, its equivalent to ShareAddress.
|
||||
*/
|
||||
|
||||
/*! \enum QAbstractSocket::PauseMode
|
||||
\since 5.0
|
||||
|
||||
This enum describes the behavior of when the socket should hold
|
||||
back with continuing data transfer.
|
||||
|
||||
\value PauseNever Do not pause data transfer on the socket. This is the
|
||||
default and matches the behaviour of Qt 4.
|
||||
\value PauseOnNotify Pause data transfer on the socket upon receiving a
|
||||
notification. The only notification currently supported is
|
||||
QSslSocket::sslErrors().
|
||||
*/
|
||||
|
||||
#include "qabstractsocket.h"
|
||||
#include "qabstractsocket_p.h"
|
||||
|
||||
@ -529,6 +542,7 @@ QAbstractSocketPrivate::QAbstractSocketPrivate()
|
||||
abortCalled(false),
|
||||
closeCalled(false),
|
||||
pendingClose(false),
|
||||
pauseMode(QAbstractSocket::PauseNever),
|
||||
port(0),
|
||||
localPort(0),
|
||||
peerPort(0),
|
||||
@ -1351,6 +1365,55 @@ QAbstractSocket::~QAbstractSocket()
|
||||
abort();
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.0
|
||||
|
||||
Continues data transfer on the socket. This method should only be used
|
||||
after the socket has been set to pause upon notifications and a
|
||||
notification has been received.
|
||||
The only notification currently supported is QSslSocket::sslErrors().
|
||||
Calling this method if the socket is not paused results in undefined
|
||||
behavior.
|
||||
|
||||
\sa pauseMode(), setPauseMode()
|
||||
*/
|
||||
void QAbstractSocket::resume()
|
||||
{
|
||||
Q_D(QAbstractSocket);
|
||||
d->resumeSocketNotifiers(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.0
|
||||
|
||||
Returns the pause mode of this socket.
|
||||
|
||||
\sa setPauseMode(), resume()
|
||||
*/
|
||||
QAbstractSocket::PauseMode QAbstractSocket::pauseMode() const
|
||||
{
|
||||
return d_func()->pauseMode;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\since 5.0
|
||||
|
||||
Controls whether to pause upon receiving a notification. The only notification
|
||||
currently supported is QSslSocket::sslErrors(). If set to PauseOnNotify,
|
||||
data transfer on the socket will be paused and needs to be enabled explicitly
|
||||
again by calling resume().
|
||||
By default this option is set to PauseNever.
|
||||
This option must be called before connecting to the server, otherwise it will
|
||||
result in undefined behavior.
|
||||
|
||||
\sa pauseMode(), resume()
|
||||
*/
|
||||
void QAbstractSocket::setPauseMode(PauseMode pauseMode)
|
||||
{
|
||||
d_func()->pauseMode = pauseMode;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.0
|
||||
|
||||
|
@ -123,10 +123,18 @@ public:
|
||||
ReuseAddressHint = 0x4
|
||||
};
|
||||
Q_DECLARE_FLAGS(BindMode, BindFlag)
|
||||
enum PauseMode {
|
||||
PauseNever,
|
||||
PauseOnNotify
|
||||
};
|
||||
|
||||
QAbstractSocket(SocketType socketType, QObject *parent);
|
||||
virtual ~QAbstractSocket();
|
||||
|
||||
virtual void resume(); // to continue after proxy authentication required, SSL errors etc.
|
||||
PauseMode pauseMode() const;
|
||||
void setPauseMode(PauseMode pauseMode);
|
||||
|
||||
bool bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform);
|
||||
bool bind(quint16 port = 0, BindMode mode = DefaultForPlatform);
|
||||
|
||||
|
@ -106,6 +106,8 @@ public:
|
||||
bool closeCalled;
|
||||
bool pendingClose;
|
||||
|
||||
QAbstractSocket::PauseMode pauseMode;
|
||||
|
||||
QString hostName;
|
||||
quint16 port;
|
||||
QHostAddress host;
|
||||
|
@ -355,6 +355,24 @@ QSslSocket::~QSslSocket()
|
||||
d->plainSocket = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
|
||||
\since 5.0
|
||||
|
||||
Continues data transfer on the socket after it has been paused. If
|
||||
"setPauseMode(QAbstractSocket::PauseOnNotify);" has been called on
|
||||
this socket and a sslErrors() signal is received, calling this method
|
||||
is necessary for the socket to continue.
|
||||
|
||||
\sa QAbstractSocket::pauseMode(), QAbstractSocket::setPauseMode()
|
||||
*/
|
||||
void QSslSocket::resume()
|
||||
{
|
||||
// continuing might emit signals, rather do this through the event loop
|
||||
QMetaObject::invokeMethod(this, "_q_resumeImplementation", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
/*!
|
||||
Starts an encrypted connection to the device \a hostName on \a
|
||||
port, using \a mode as the \l OpenMode. This is equivalent to
|
||||
@ -1860,6 +1878,7 @@ QSslSocketPrivate::QSslSocketPrivate()
|
||||
, readyReadEmittedPointer(0)
|
||||
, allowRootCertOnDemandLoading(true)
|
||||
, plainSocket(0)
|
||||
, paused(false)
|
||||
{
|
||||
QSslConfigurationPrivate::deepCopyDefaultConfiguration(&configuration);
|
||||
}
|
||||
@ -2114,6 +2133,11 @@ void QSslSocketPrivate::resumeSocketNotifiers(QSslSocket *socket)
|
||||
QAbstractSocketPrivate::resumeSocketNotifiers(socket->d_func()->plainSocket);
|
||||
}
|
||||
|
||||
bool QSslSocketPrivate::isPaused() const
|
||||
{
|
||||
return paused;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
@ -2257,6 +2281,55 @@ void QSslSocketPrivate::_q_flushReadBuffer()
|
||||
transmit();
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
void QSslSocketPrivate::_q_resumeImplementation()
|
||||
{
|
||||
Q_Q(QSslSocket);
|
||||
if (plainSocket)
|
||||
plainSocket->resume();
|
||||
paused = false;
|
||||
if (!connectionEncrypted) {
|
||||
if (verifyErrorsHaveBeenIgnored()) {
|
||||
continueHandshake();
|
||||
} else {
|
||||
q->setErrorString(sslErrors.first().errorString());
|
||||
q->setSocketError(QAbstractSocket::SslHandshakeFailedError);
|
||||
emit q->error(QAbstractSocket::SslHandshakeFailedError);
|
||||
plainSocket->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
}
|
||||
transmit();
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
bool QSslSocketPrivate::verifyErrorsHaveBeenIgnored()
|
||||
{
|
||||
bool doEmitSslError;
|
||||
if (!ignoreErrorsList.empty()) {
|
||||
// check whether the errors we got are all in the list of expected errors
|
||||
// (applies only if the method QSslSocket::ignoreSslErrors(const QList<QSslError> &errors)
|
||||
// was called)
|
||||
doEmitSslError = false;
|
||||
for (int a = 0; a < sslErrors.count(); a++) {
|
||||
if (!ignoreErrorsList.contains(sslErrors.at(a))) {
|
||||
doEmitSslError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if QSslSocket::ignoreSslErrors(const QList<QSslError> &errors) was not called and
|
||||
// we get an SSL error, emit a signal unless we ignored all errors (by calling
|
||||
// QSslSocket::ignoreSslErrors() )
|
||||
doEmitSslError = !ignoreAllSslErrors;
|
||||
}
|
||||
return !doEmitSslError;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
|
@ -82,6 +82,7 @@ public:
|
||||
|
||||
QSslSocket(QObject *parent = 0);
|
||||
~QSslSocket();
|
||||
void resume(); // to continue after proxy authentication required, SSL errors etc.
|
||||
|
||||
// Autostarting the SSL client handshake.
|
||||
void connectToHostEncrypted(const QString &hostName, quint16 port, OpenMode mode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
|
||||
@ -211,6 +212,7 @@ private:
|
||||
Q_PRIVATE_SLOT(d_func(), void _q_bytesWrittenSlot(qint64))
|
||||
Q_PRIVATE_SLOT(d_func(), void _q_flushWriteBuffer())
|
||||
Q_PRIVATE_SLOT(d_func(), void _q_flushReadBuffer())
|
||||
Q_PRIVATE_SLOT(d_func(), void _q_resumeImplementation())
|
||||
friend class QSslSocketBackendPrivate;
|
||||
};
|
||||
|
||||
|
@ -295,6 +295,7 @@ bool QSslSocketBackendPrivate::initSslContext()
|
||||
bool client = (mode == QSslSocket::SslClientMode);
|
||||
|
||||
bool reinitialized = false;
|
||||
|
||||
init_context:
|
||||
switch (configuration.protocol) {
|
||||
case QSsl::SslV2:
|
||||
@ -950,6 +951,9 @@ void QSslSocketBackendPrivate::transmit()
|
||||
qDebug() << "QSslSocketBackendPrivate::transmit: connection lost";
|
||||
#endif
|
||||
break;
|
||||
} else if (paused) {
|
||||
// just wait until the user continues
|
||||
return;
|
||||
} else {
|
||||
#ifdef QSSLSOCKET_DEBUG
|
||||
qDebug() << "QSslSocketBackendPrivate::transmit: encryption not done yet";
|
||||
@ -1188,46 +1192,25 @@ bool QSslSocketBackendPrivate::startHandshake()
|
||||
sslErrors = errors;
|
||||
emit q->sslErrors(errors);
|
||||
|
||||
bool doEmitSslError;
|
||||
if (!ignoreErrorsList.empty()) {
|
||||
// check whether the errors we got are all in the list of expected errors
|
||||
// (applies only if the method QSslSocket::ignoreSslErrors(const QList<QSslError> &errors)
|
||||
// was called)
|
||||
doEmitSslError = false;
|
||||
for (int a = 0; a < errors.count(); a++) {
|
||||
if (!ignoreErrorsList.contains(errors.at(a))) {
|
||||
doEmitSslError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if QSslSocket::ignoreSslErrors(const QList<QSslError> &errors) was not called and
|
||||
// we get an SSL error, emit a signal unless we ignored all errors (by calling
|
||||
// QSslSocket::ignoreSslErrors() )
|
||||
doEmitSslError = !ignoreAllSslErrors;
|
||||
}
|
||||
bool doEmitSslError = !verifyErrorsHaveBeenIgnored();
|
||||
// check whether we need to emit an SSL handshake error
|
||||
if (doVerifyPeer && doEmitSslError) {
|
||||
q->setErrorString(sslErrors.first().errorString());
|
||||
q->setSocketError(QAbstractSocket::SslHandshakeFailedError);
|
||||
emit q->error(QAbstractSocket::SslHandshakeFailedError);
|
||||
plainSocket->disconnectFromHost();
|
||||
if (q->pauseMode() == QAbstractSocket::PauseOnNotify) {
|
||||
pauseSocketNotifiers(q);
|
||||
paused = true;
|
||||
} else {
|
||||
q->setErrorString(sslErrors.first().errorString());
|
||||
q->setSocketError(QAbstractSocket::SslHandshakeFailedError);
|
||||
emit q->error(QAbstractSocket::SslHandshakeFailedError);
|
||||
plainSocket->disconnectFromHost();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sslErrors.clear();
|
||||
}
|
||||
|
||||
// if we have a max read buffer size, reset the plain socket's to 1k
|
||||
if (readBufferMaxSize)
|
||||
plainSocket->setReadBufferSize(1024);
|
||||
|
||||
connectionEncrypted = true;
|
||||
emit q->encrypted();
|
||||
if (autoStartHandshake && pendingClose) {
|
||||
pendingClose = false;
|
||||
q->disconnectFromHost();
|
||||
}
|
||||
continueHandshake();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1271,6 +1254,21 @@ QSslCipher QSslSocketBackendPrivate::sessionCipher() const
|
||||
return sessionCipher ? QSslCipher_from_SSL_CIPHER(sessionCipher) : QSslCipher();
|
||||
}
|
||||
|
||||
void QSslSocketBackendPrivate::continueHandshake()
|
||||
{
|
||||
Q_Q(QSslSocket);
|
||||
// if we have a max read buffer size, reset the plain socket's to match
|
||||
if (readBufferMaxSize)
|
||||
plainSocket->setReadBufferSize(readBufferMaxSize);
|
||||
|
||||
connectionEncrypted = true;
|
||||
emit q->encrypted();
|
||||
if (autoStartHandshake && pendingClose) {
|
||||
pendingClose = false;
|
||||
q->disconnectFromHost();
|
||||
}
|
||||
}
|
||||
|
||||
QList<QSslCertificate> QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates(STACK_OF(X509) *x509)
|
||||
{
|
||||
ensureInitialized();
|
||||
|
@ -115,6 +115,7 @@ public:
|
||||
void disconnectFromHost();
|
||||
void disconnected();
|
||||
QSslCipher sessionCipher() const;
|
||||
void continueHandshake();
|
||||
|
||||
Q_AUTOTEST_EXPORT static long setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions);
|
||||
static QSslCipher QSslCipher_from_SSL_CIPHER(SSL_CIPHER *cipher);
|
||||
|
@ -149,6 +149,7 @@ public:
|
||||
void createPlainSocket(QIODevice::OpenMode openMode);
|
||||
static void pauseSocketNotifiers(QSslSocket*);
|
||||
static void resumeSocketNotifiers(QSslSocket*);
|
||||
bool isPaused() const;
|
||||
void _q_connectedSlot();
|
||||
void _q_hostFoundSlot();
|
||||
void _q_disconnectedSlot();
|
||||
@ -158,6 +159,7 @@ public:
|
||||
void _q_bytesWrittenSlot(qint64);
|
||||
void _q_flushWriteBuffer();
|
||||
void _q_flushReadBuffer();
|
||||
void _q_resumeImplementation();
|
||||
|
||||
// Platform specific functions
|
||||
virtual void startClientEncryption() = 0;
|
||||
@ -166,6 +168,7 @@ public:
|
||||
virtual void disconnectFromHost() = 0;
|
||||
virtual void disconnected() = 0;
|
||||
virtual QSslCipher sessionCipher() const = 0;
|
||||
virtual void continueHandshake() = 0;
|
||||
|
||||
private:
|
||||
static bool ensureLibraryLoaded();
|
||||
@ -174,8 +177,10 @@ private:
|
||||
static bool s_libraryLoaded;
|
||||
static bool s_loadedCiphersAndCerts;
|
||||
protected:
|
||||
bool verifyErrorsHaveBeenIgnored();
|
||||
static bool s_loadRootCertsOnDemand;
|
||||
static QList<QByteArray> unixRootCertDirectories();
|
||||
bool paused;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -192,10 +192,12 @@ private slots:
|
||||
void readFromClosedSocket();
|
||||
void writeBigChunk();
|
||||
void blacklistedCertificates();
|
||||
void setEmptyDefaultConfiguration();
|
||||
void versionAccessors();
|
||||
void sslOptions();
|
||||
void encryptWithoutConnecting();
|
||||
void resume_data();
|
||||
void resume();
|
||||
void setEmptyDefaultConfiguration(); // this test should be last
|
||||
|
||||
static void exitLoop()
|
||||
{
|
||||
@ -2058,22 +2060,6 @@ void tst_QSslSocket::blacklistedCertificates()
|
||||
QCOMPARE(sslErrors.at(0).error(), QSslError::CertificateBlacklisted);
|
||||
}
|
||||
|
||||
void tst_QSslSocket::setEmptyDefaultConfiguration()
|
||||
{
|
||||
// used to produce a crash in QSslConfigurationPrivate::deepCopyDefaultConfiguration, QTBUG-13265
|
||||
|
||||
if (!QSslSocket::supportsSsl())
|
||||
return;
|
||||
|
||||
QSslConfiguration emptyConf;
|
||||
QSslConfiguration::setDefaultConfiguration(emptyConf);
|
||||
|
||||
QSslSocketPtr socket = newSocket();
|
||||
connect(socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(ignoreErrorSlot()));
|
||||
socket->connectToHostEncrypted(QtNetworkSettings::serverName(), 443);
|
||||
QVERIFY2(!socket->waitForEncrypted(4000), qPrintable(socket->errorString()));
|
||||
}
|
||||
|
||||
void tst_QSslSocket::versionAccessors()
|
||||
{
|
||||
if (!QSslSocket::supportsSsl())
|
||||
@ -2141,7 +2127,97 @@ void tst_QSslSocket::encryptWithoutConnecting()
|
||||
sock.startClientEncryption();
|
||||
}
|
||||
|
||||
void tst_QSslSocket::resume_data()
|
||||
{
|
||||
QTest::addColumn<bool>("ignoreErrorsAfterPause");
|
||||
QTest::addColumn<QList<QSslError> >("errorsToIgnore");
|
||||
QTest::addColumn<bool>("expectSuccess");
|
||||
|
||||
QList<QSslError> errorsList;
|
||||
QTest::newRow("DoNotIgnoreErrors") << false << QList<QSslError>() << false;
|
||||
QTest::newRow("ignoreAllErrors") << true << QList<QSslError>() << true;
|
||||
|
||||
QList<QSslCertificate> certs = QSslCertificate::fromPath(QLatin1String(SRCDIR "certs/qt-test-server-cacert.pem"));
|
||||
QSslError rightError(QSslError::SelfSignedCertificate, certs.at(0));
|
||||
QSslError wrongError(QSslError::SelfSignedCertificate);
|
||||
errorsList.append(wrongError);
|
||||
QTest::newRow("ignoreSpecificErrors-Wrong") << true << errorsList << false;
|
||||
errorsList.clear();
|
||||
errorsList.append(rightError);
|
||||
QTest::newRow("ignoreSpecificErrors-Right") << true << errorsList << true;
|
||||
}
|
||||
|
||||
void tst_QSslSocket::resume()
|
||||
{
|
||||
// make sure the server certificate is not in the list of accepted certificates,
|
||||
// we want to trigger the sslErrors signal
|
||||
QSslSocket::setDefaultCaCertificates(QSslSocket::systemCaCertificates());
|
||||
|
||||
QFETCH(bool, ignoreErrorsAfterPause);
|
||||
QFETCH(QList<QSslError>, errorsToIgnore);
|
||||
QFETCH(bool, expectSuccess);
|
||||
|
||||
QSslSocket socket;
|
||||
socket.setPauseMode(QAbstractSocket::PauseOnNotify);
|
||||
|
||||
QSignalSpy sslErrorSpy(&socket, SIGNAL(sslErrors(QList<QSslError>)));
|
||||
QSignalSpy encryptedSpy(&socket, SIGNAL(encrypted()));
|
||||
QSignalSpy errorSpy(&socket, SIGNAL(error(QAbstractSocket::SocketError)));
|
||||
|
||||
connect(&socket, SIGNAL(sslErrors(QList<QSslError>)), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||
connect(&socket, SIGNAL(encrypted()), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||
connect(&socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
|
||||
this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
|
||||
connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||
|
||||
socket.connectToHostEncrypted(QtNetworkSettings::serverName(), 993);
|
||||
QTestEventLoop::instance().enterLoop(10);
|
||||
QVERIFY(!QTestEventLoop::instance().timeout());
|
||||
QCOMPARE(sslErrorSpy.count(), 1);
|
||||
QCOMPARE(errorSpy.count(), 0);
|
||||
QCOMPARE(encryptedSpy.count(), 0);
|
||||
QVERIFY(!socket.isEncrypted());
|
||||
if (ignoreErrorsAfterPause) {
|
||||
if (errorsToIgnore.empty())
|
||||
socket.ignoreSslErrors();
|
||||
else
|
||||
socket.ignoreSslErrors(errorsToIgnore);
|
||||
}
|
||||
socket.resume();
|
||||
QTestEventLoop::instance().enterLoop(10);
|
||||
QVERIFY(!QTestEventLoop::instance().timeout()); // quit by encrypted() or error() signal
|
||||
if (expectSuccess) {
|
||||
QCOMPARE(encryptedSpy.count(), 1);
|
||||
QVERIFY(socket.isEncrypted());
|
||||
QCOMPARE(errorSpy.count(), 0);
|
||||
socket.disconnectFromHost();
|
||||
QVERIFY(socket.waitForDisconnected(10000));
|
||||
} else {
|
||||
QCOMPARE(encryptedSpy.count(), 0);
|
||||
QVERIFY(!socket.isEncrypted());
|
||||
QCOMPARE(errorSpy.count(), 1);
|
||||
QCOMPARE(socket.error(), QAbstractSocket::SslHandshakeFailedError);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if (!QSslSocket::supportsSsl())
|
||||
return;
|
||||
|
||||
QSslConfiguration emptyConf;
|
||||
QSslConfiguration::setDefaultConfiguration(emptyConf);
|
||||
|
||||
QSslSocketPtr socket = newSocket();
|
||||
connect(socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(ignoreErrorSlot()));
|
||||
socket->connectToHostEncrypted(QtNetworkSettings::serverName(), 443);
|
||||
QVERIFY2(!socket->waitForEncrypted(4000), qPrintable(socket->errorString()));
|
||||
}
|
||||
|
||||
#endif // QT_NO_OPENSSL
|
||||
|
||||
QTEST_MAIN(tst_QSslSocket)
|
||||
|
||||
#include "tst_qsslsocket.moc"
|
||||
|
Loading…
Reference in New Issue
Block a user