QNAM: delay SSL initialization

It's really unfortunate that even a plain 'http' request results in
(Open)SSL initialization; this is apparently done by QSslConfiguration's
default constructor and we have several classes including QSslConfiguration
as a data-member.

There are different problems reported because of this, from crashes
(a broken OpenSSL on Windows) to long initialization times, which is
not acceptable if no 'https' request was actually executed.

This patch-set is replacing data-members of type QSslConfiguration
with smart-pointers and delays (Open)SSL initialization.

Task-number: QTBUG-59750
Change-Id: Id1d375e689dbd2d134abbb0572a9e804d595110e
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Timur Pocheptsov 2017-03-27 15:55:34 +02:00
parent 4ba740b3ba
commit 70d8460fc2
6 changed files with 43 additions and 22 deletions

View File

@ -55,7 +55,6 @@
# include <private/qsslsocket_p.h>
# include <QtNetwork/qsslkey.h>
# include <QtNetwork/qsslcipher.h>
# include <QtNetwork/qsslconfiguration.h>
#endif
#ifndef QT_NO_BEARERMANAGEMENT
@ -176,8 +175,8 @@ void QHttpNetworkConnectionChannel::init()
if (!ignoreSslErrorsList.isEmpty())
sslSocket->ignoreSslErrors(ignoreSslErrorsList);
if (!sslConfiguration.isNull())
sslSocket->setSslConfiguration(sslConfiguration);
if (sslConfiguration.data() && !sslConfiguration->isNull())
sslSocket->setSslConfiguration(*sslConfiguration);
} else {
#endif // !QT_NO_SSL
if (connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2)
@ -656,7 +655,10 @@ void QHttpNetworkConnectionChannel::setSslConfiguration(const QSslConfiguration
if (socket)
static_cast<QSslSocket *>(socket)->setSslConfiguration(config);
sslConfiguration = config;
if (sslConfiguration.data())
*sslConfiguration = config;
else
sslConfiguration.reset(new QSslConfiguration(config));
}
#endif
@ -1085,8 +1087,15 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
Q_FALLTHROUGH();
case QSslConfiguration::NextProtocolNegotiationNone: {
protocolHandler.reset(new QHttpProtocolHandler(this));
if (!sslConfiguration.data()) {
// Our own auto-tests bypass the normal initialization (done by
// QHttpThreadDelegate), this means in the past we'd have here
// the default constructed QSslConfiguration without any protocols
// to negotiate. Let's create it now:
sslConfiguration.reset(new QSslConfiguration);
}
QList<QByteArray> protocols = sslConfiguration.allowedNextProtocols();
QList<QByteArray> protocols = sslConfiguration->allowedNextProtocols();
const int nProtocols = protocols.size();
// Clear the protocol that we failed to negotiate, so we do not try
// it again on other channels that our connection can create/open.
@ -1096,10 +1105,10 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
protocols.removeAll(QSslConfiguration::NextProtocolSpdy3_0);
if (nProtocols > protocols.size()) {
sslConfiguration.setAllowedNextProtocols(protocols);
sslConfiguration->setAllowedNextProtocols(protocols);
const int channelCount = connection->d_func()->channelCount;
for (int i = 0; i < channelCount; ++i)
connection->d_func()->channels[i].setSslConfiguration(sslConfiguration);
connection->d_func()->channels[i].setSslConfiguration(*sslConfiguration);
}
connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP);

View File

@ -78,6 +78,8 @@
# include <QtNetwork/qtcpsocket.h>
#endif
#include <QtCore/qscopedpointer.h>
QT_BEGIN_NAMESPACE
class QHttpNetworkRequest;
@ -128,7 +130,7 @@ public:
#ifndef QT_NO_SSL
bool ignoreAllSslErrors;
QList<QSslError> ignoreSslErrorsList;
QSslConfiguration sslConfiguration;
QScopedPointer<QSslConfiguration> sslConfiguration;
void ignoreSslErrors();
void ignoreSslErrors(const QList<QSslError> &errors);
void setSslConfiguration(const QSslConfiguration &config);

View File

@ -293,19 +293,22 @@ void QHttpThreadDelegate::startRequest()
= httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2
: QHttpNetworkConnection::ConnectionTypeHTTP;
if (ssl && !incomingSslConfiguration.data())
incomingSslConfiguration.reset(new QSslConfiguration);
#ifndef QT_NO_SSL
if (httpRequest.isHTTP2Allowed() && ssl) {
QList<QByteArray> protocols;
protocols << QSslConfiguration::ALPNProtocolHTTP2
<< QSslConfiguration::NextProtocolHttp1_1;
incomingSslConfiguration.setAllowedNextProtocols(protocols);
incomingSslConfiguration->setAllowedNextProtocols(protocols);
} else if (httpRequest.isSPDYAllowed() && ssl) {
connectionType = QHttpNetworkConnection::ConnectionTypeSPDY;
urlCopy.setScheme(QStringLiteral("spdy")); // to differentiate SPDY requests from HTTPS requests
QList<QByteArray> nextProtocols;
nextProtocols << QSslConfiguration::NextProtocolSpdy3_0
<< QSslConfiguration::NextProtocolHttp1_1;
incomingSslConfiguration.setAllowedNextProtocols(nextProtocols);
incomingSslConfiguration->setAllowedNextProtocols(nextProtocols);
}
#endif // QT_NO_SSL
@ -334,9 +337,8 @@ void QHttpThreadDelegate::startRequest()
#endif
#ifndef QT_NO_SSL
// Set the QSslConfiguration from this QNetworkRequest.
if (ssl && incomingSslConfiguration != QSslConfiguration::defaultConfiguration()) {
httpConnection->setSslConfiguration(incomingSslConfiguration);
}
if (ssl)
httpConnection->setSslConfiguration(*incomingSslConfiguration);
#endif
#ifndef QT_NO_NETWORKPROXY

View File

@ -63,7 +63,7 @@
#include "qhttpnetworkrequest_p.h"
#include "qhttpnetworkconnection_p.h"
#include <QSharedPointer>
#include "qsslconfiguration.h"
#include <QScopedPointer>
#include "private/qnoncontiguousbytedevice_p.h"
#include "qnetworkaccessauthenticationmanager_p.h"
@ -88,7 +88,7 @@ public:
// incoming
bool ssl;
#ifndef QT_NO_SSL
QSslConfiguration incomingSslConfiguration;
QScopedPointer<QSslConfiguration> incomingSslConfiguration;
#endif
QHttpNetworkRequest httpRequest;
qint64 downloadBufferMaximumSize;

View File

@ -180,7 +180,8 @@ QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manage
d->outgoingData = outgoingData;
d->url = request.url();
#ifndef QT_NO_SSL
d->sslConfiguration = request.sslConfiguration();
if (request.url().scheme() == QLatin1String("https"))
d->sslConfiguration.reset(new QSslConfiguration(request.sslConfiguration()));
#endif
// FIXME Later maybe set to Unbuffered, especially if it is zerocopy or from cache?
@ -419,7 +420,10 @@ void QNetworkReplyHttpImpl::setSslConfigurationImplementation(const QSslConfigur
void QNetworkReplyHttpImpl::sslConfigurationImplementation(QSslConfiguration &configuration) const
{
Q_D(const QNetworkReplyHttpImpl);
configuration = d->sslConfiguration;
if (d->sslConfiguration.data())
configuration = *d->sslConfiguration;
else
configuration = request().sslConfiguration();
}
#endif
@ -786,7 +790,7 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
delegate->ssl = ssl;
#ifndef QT_NO_SSL
if (ssl)
delegate->incomingSslConfiguration = newHttpRequest.sslConfiguration();
delegate->incomingSslConfiguration.reset(new QSslConfiguration(newHttpRequest.sslConfiguration()));
#endif
// Do we use synchronous HTTP?
@ -1411,10 +1415,13 @@ void QNetworkReplyHttpImplPrivate::replySslErrors(
*toBeIgnored = pendingIgnoreSslErrorsList;
}
void QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfiguration &sslConfiguration)
void QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfiguration &newSslConfiguration)
{
// Receiving the used SSL configuration from the HTTP thread
this->sslConfiguration = sslConfiguration;
if (sslConfiguration.data())
*sslConfiguration = newSslConfiguration;
else
sslConfiguration.reset(new QSslConfiguration(newSslConfiguration));
}
void QNetworkReplyHttpImplPrivate::replyPreSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator *authenticator)

View File

@ -58,6 +58,7 @@
#include "QtCore/qpointer.h"
#include "QtCore/qdatetime.h"
#include "QtCore/qsharedpointer.h"
#include "QtCore/qscopedpointer.h"
#include "qatomic.h"
#include <QtNetwork/QNetworkCacheMetaData>
@ -260,7 +261,7 @@ public:
#ifndef QT_NO_SSL
QSslConfiguration sslConfiguration;
QScopedPointer<QSslConfiguration> sslConfiguration;
bool pendingIgnoreAllSslErrors;
QList<QSslError> pendingIgnoreSslErrorsList;
#endif
@ -290,7 +291,7 @@ public:
#ifndef QT_NO_SSL
void replyEncrypted();
void replySslErrors(const QList<QSslError> &, bool *, QList<QSslError> *);
void replySslConfigurationChanged(const QSslConfiguration&);
void replySslConfigurationChanged(const QSslConfiguration &newSslConfiguration);
void replyPreSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator *);
#endif
#ifndef QT_NO_NETWORKPROXY