Add an encrypted() signal to QNetworkAccessManager and QNetworkReply.

Add an encrypted signal to QNAM and QNetworkReply to allow applications
to perform additional checks on the certificate chain beyond those done
as part of the standard SSL validation. This allows things like
certificate change notification to be implemented for QNAM as they can
be for QSSLSocket currently.

Change-Id: I693e3e6fec8b7040379b7e7f1f819550e6b2617f
Reviewed-by: Peter Hartmann <phartmann@rim.com>
bb10
Richard Moore 2013-02-10 17:06:34 +00:00 committed by The Qt Project
parent 7898080ca7
commit 5ebc8d3663
16 changed files with 126 additions and 0 deletions

View File

@ -1224,6 +1224,8 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
pendingEncrypt = false;
if (!reply)
connection->d_func()->dequeueRequest(socket);
if (reply)
emit reply->encrypted();
if (reply)
sendRequest();
}

View File

@ -142,6 +142,7 @@ public:
void ignoreSslErrors(const QList<QSslError> &errors);
Q_SIGNALS:
void encrypted();
void sslErrors(const QList<QSslError> &errors);
#endif

View File

@ -315,6 +315,7 @@ void QHttpThreadDelegate::startRequest()
connect(httpReply,SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
connect(httpReply,SIGNAL(dataReadProgress(qint64,qint64)), this, SLOT(dataReadProgressSlot(qint64,qint64)));
#ifndef QT_NO_SSL
connect(httpReply,SIGNAL(encrypted()), this, SLOT(encryptedSlot()));
connect(httpReply,SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrorsSlot(QList<QSslError>)));
#endif
@ -589,6 +590,14 @@ void QHttpThreadDelegate::cacheCredentialsSlot(const QHttpNetworkRequest &reques
#ifndef QT_NO_SSL
void QHttpThreadDelegate::encryptedSlot()
{
if (!httpReply)
return;
emit encrypted();
}
void QHttpThreadDelegate::sslErrorsSlot(const QList<QSslError> &errors)
{
if (!httpReply)

View File

@ -135,6 +135,7 @@ signals:
void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *);
#endif
#ifndef QT_NO_SSL
void encrypted();
void sslErrors(const QList<QSslError> &, bool *, QList<QSslError> *);
void sslConfigurationChanged(const QSslConfiguration);
#endif
@ -164,6 +165,7 @@ protected slots:
void dataReadProgressSlot(qint64 done, qint64 total);
void cacheCredentialsSlot(const QHttpNetworkRequest &request, QAuthenticator *authenticator);
#ifndef QT_NO_SSL
void encryptedSlot();
void sslErrorsSlot(const QList<QSslError> &errors);
#endif

View File

@ -340,6 +340,13 @@ void QNetworkAccessBackend::redirectionRequested(const QUrl &target)
reply->redirectionRequested(target);
}
void QNetworkAccessBackend::encrypted()
{
#ifndef QT_NO_SSL
reply->encrypted();
#endif
}
void QNetworkAccessBackend::sslErrors(const QList<QSslError> &errors)
{
#ifndef QT_NO_SSL

View File

@ -196,6 +196,7 @@ protected slots:
void authenticationRequired(QAuthenticator *auth);
void metaDataChanged();
void redirectionRequested(const QUrl &destination);
void encrypted();
void sslErrors(const QList<QSslError> &errors);
void emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal);

View File

@ -373,6 +373,32 @@ static void ensureInitialized()
\sa QNetworkReply::finished(), QNetworkReply::error()
*/
/*!
\fn void QNetworkAccessManager::encrypted(QNetworkReply *reply)
\since 5.1
This signal is emitted when an SSL/TLS session has successfully
completed the initial handshake. At this point, no user data
has been transmitted. The signal can be used to perform
additional checks on the certificate chain, for example to
notify users when the certificate for a website has changed. The
\a reply parameter specifies which network reply is responsible.
If the reply does not match the expected criteria then it should
be aborted by calling QNetworkReply::abort() by a slot connected
to this signal. The SSL configuration in use can be inspected
using the QNetworkReply::sslConfiguration() method.
Internally, QNetworkAccessManager may open multiple connections
to a server, in order to allow it process requests in parallel.
These connections may be reused, which means that the encrypted()
signal would not be emitted. This means that you are only
guaranteed to receive this signal for the first connection to a
site in the lifespan of the QNetworkAccessManager.
\sa QSslSocket::encrypted()
\sa QNetworkReply::encrypted()
*/
/*!
\fn void QNetworkAccessManager::sslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
@ -1132,6 +1158,16 @@ void QNetworkAccessManagerPrivate::_q_replyFinished()
#endif
}
void QNetworkAccessManagerPrivate::_q_replyEncrypted()
{
#ifndef QT_NO_SSL
Q_Q(QNetworkAccessManager);
QNetworkReply *reply = qobject_cast<QNetworkReply *>(q->sender());
if (reply)
emit q->encrypted(reply);
#endif
}
void QNetworkAccessManagerPrivate::_q_replySslErrors(const QList<QSslError> &errors)
{
#ifndef QT_NO_SSL
@ -1152,6 +1188,7 @@ QNetworkReply *QNetworkAccessManagerPrivate::postProcess(QNetworkReply *reply)
#ifndef QT_NO_SSL
/* In case we're compiled without SSL support, we don't have this signal and we need to
* avoid getting a connection error. */
q->connect(reply, SIGNAL(encrypted()), SLOT(_q_replyEncrypted()));
q->connect(reply, SIGNAL(sslErrors(QList<QSslError>)), SLOT(_q_replySslErrors(QList<QSslError>)));
#endif
#ifndef QT_NO_BEARERMANAGEMENT

View File

@ -139,6 +139,7 @@ Q_SIGNALS:
void authenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator);
void finished(QNetworkReply *reply);
#ifndef QT_NO_SSL
void encrypted(QNetworkReply *reply);
void sslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
#endif
@ -159,6 +160,7 @@ private:
Q_DECLARE_PRIVATE(QNetworkAccessManager)
Q_PRIVATE_SLOT(d_func(), void _q_replyFinished())
Q_PRIVATE_SLOT(d_func(), void _q_replyEncrypted())
Q_PRIVATE_SLOT(d_func(), void _q_replySslErrors(QList<QSslError>))
#ifndef QT_NO_BEARERMANAGEMENT
Q_PRIVATE_SLOT(d_func(), void _q_networkSessionClosed())

View File

@ -90,6 +90,7 @@ public:
~QNetworkAccessManagerPrivate();
void _q_replyFinished();
void _q_replyEncrypted();
void _q_replySslErrors(const QList<QSslError> &errors);
QNetworkReply *postProcess(QNetworkReply *reply);
void createCookieJar() const;

View File

@ -194,6 +194,31 @@ QNetworkReplyPrivate::QNetworkReplyPrivate()
\sa error()
*/
/*!
\fn void QNetworkReply::encrypted()
\since 5.1
This signal is emitted when an SSL/TLS session has successfully
completed the initial handshake. At this point, no user data
has been transmitted. The signal can be used to perform
additional checks on the certificate chain, for example to
notify users when the certificate for a website has changed.
If the reply does not match the expected criteria then it should
be aborted by calling QNetworkReply::abort() by a slot connected
to this signal. The SSL configuration in use can be inspected
using the QNetworkReply::sslConfiguration() method.
Internally, QNetworkAccessManager may open multiple connections
to a server, in order to allow it process requests in parallel.
These connections may be reused, which means that the encrypted()
signal would not be emitted. This means that you are only
guaranteed to receive this signal for the first connection to a
site in the lifespan of the QNetworkAccessManager.
\sa QSslSocket::encrypted()
\sa QNetworkAccessManager::encrypted()
*/
/*!
\fn void QNetworkReply::sslErrors(const QList<QSslError> &errors)

View File

@ -148,6 +148,7 @@ Q_SIGNALS:
void finished();
void error(QNetworkReply::NetworkError);
#ifndef QT_NO_SSL
void encrypted();
void sslErrors(const QList<QSslError> &errors);
#endif

View File

@ -830,6 +830,8 @@ void QNetworkReplyHttpImplPrivate::postRequest()
Qt::BlockingQueuedConnection);
#endif
#ifndef QT_NO_SSL
QObject::connect(delegate, SIGNAL(encrypted()), q, SLOT(replyEncrypted()),
Qt::BlockingQueuedConnection);
QObject::connect(delegate, SIGNAL(sslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
q, SLOT(replySslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
Qt::BlockingQueuedConnection);
@ -1220,6 +1222,12 @@ void QNetworkReplyHttpImplPrivate::httpError(QNetworkReply::NetworkError errorCo
}
#ifndef QT_NO_SSL
void QNetworkReplyHttpImplPrivate::replyEncrypted()
{
Q_Q(QNetworkReplyHttpImpl);
emit q->encrypted();
}
void QNetworkReplyHttpImplPrivate::replySslErrors(
const QList<QSslError> &list, bool *ignoreAll, QList<QSslError> *toBeIgnored)
{

View File

@ -116,6 +116,7 @@ public:
Q_PRIVATE_SLOT(d_func(), void httpAuthenticationRequired(const QHttpNetworkRequest &, QAuthenticator *))
Q_PRIVATE_SLOT(d_func(), void httpError(QNetworkReply::NetworkError, const QString &))
#ifndef QT_NO_SSL
Q_PRIVATE_SLOT(d_func(), void replyEncrypted())
Q_PRIVATE_SLOT(d_func(), void replySslErrors(const QList<QSslError> &, bool *, QList<QSslError> *))
Q_PRIVATE_SLOT(d_func(), void replySslConfigurationChanged(const QSslConfiguration&))
#endif
@ -280,6 +281,7 @@ public:
void httpAuthenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *auth);
void httpError(QNetworkReply::NetworkError error, const QString &errorString);
#ifndef QT_NO_SSL
void replyEncrypted();
void replySslErrors(const QList<QSslError> &, bool *, QList<QSslError> *);
void replySslConfigurationChanged(const QSslConfiguration&);
#endif

View File

@ -879,6 +879,14 @@ void QNetworkReplyImplPrivate::redirectionRequested(const QUrl &target)
attributes.insert(QNetworkRequest::RedirectionTargetAttribute, target);
}
void QNetworkReplyImplPrivate::encrypted()
{
#ifndef QT_NO_SSL
Q_Q(QNetworkReplyImpl);
emit q->encrypted();
#endif
}
void QNetworkReplyImplPrivate::sslErrors(const QList<QSslError> &errors)
{
#ifndef QT_NO_SSL

View File

@ -176,6 +176,7 @@ public:
void error(QNetworkReply::NetworkError code, const QString &errorString);
void metaDataChanged();
void redirectionRequested(const QUrl &target);
void encrypted();
void sslErrors(const QList<QSslError> &errors);
QNetworkAccessBackend *backend;

View File

@ -361,6 +361,7 @@ private Q_SLOTS:
void ignoreSslErrorsList();
void ignoreSslErrorsListWithSlot_data();
void ignoreSslErrorsListWithSlot();
void encrypted();
void sslConfiguration_data();
void sslConfiguration();
#ifdef QT_BUILD_INTERNAL
@ -5867,6 +5868,24 @@ void tst_QNetworkReply::sslConfiguration_data()
QTest::newRow("secure") << conf << true;
}
void tst_QNetworkReply::encrypted()
{
qDebug() << QtNetworkSettings::serverName();
QUrl url("https://" + QtNetworkSettings::serverName());
QNetworkRequest request(url);
QNetworkReply *reply = manager.get(request);
reply->ignoreSslErrors();
QSignalSpy spy(reply, SIGNAL(encrypted()));
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(20);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(spy.count(), 1);
reply->deleteLater();
}
void tst_QNetworkReply::sslConfiguration()
{
QNetworkRequest request(QUrl("https://" + QtNetworkSettings::serverName() + "/index.html"));