From 8b9d246225dcd63900399297b0fd553918840bea Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Mon, 20 Feb 2017 10:34:47 +0100 Subject: QHttpNetworkConnection: fall back gracefully to HTTP/1.1 Both SPDY and HTTP/2 work with a single qhttpnetworkchannel (and this means one socket per qhttpnetworkconnection). Normally, HTTP/1.1 connection is using up to 6 channels/sockets though. At the moment a failure to negotiate SPDY/HTTP/2 leaves us with a downgraded HTTP/1.1 connection (with only one channel vs. default 6). Since we initialize channels (and establish connections) in a 'lazy' manner it's ok to pre-allocate all 6 channels and then either use 1 (if SPDY/HTTP/2 indeed was negotiated) or switch back to 6 in case of failure. Change-Id: Ia6c3061463c4d634aaed05ce0dde47bfb5e24dd8 Reviewed-by: Edward Welbourne Reviewed-by: Timur Pocheptsov --- src/network/access/qhttpnetworkconnection.cpp | 54 ++++++++++++---------- src/network/access/qhttpnetworkconnection_p.h | 3 ++ .../access/qhttpnetworkconnectionchannel.cpp | 30 ++++++++++-- 3 files changed, 59 insertions(+), 28 deletions(-) (limited to 'src/network') diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 128f75f93b..1f14049a44 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -82,27 +82,31 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &host : state(RunningState), networkLayerState(Unknown), hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true) + , activeChannelCount(type == QHttpNetworkConnection::ConnectionTypeHTTP2 #ifndef QT_NO_SSL -, channelCount((type == QHttpNetworkConnection::ConnectionTypeSPDY || type == QHttpNetworkConnection::ConnectionTypeHTTP2) - ? 1 : defaultHttpChannelCount) -#else -, channelCount(type == QHttpNetworkConnection::ConnectionTypeHTTP2 ? 1 : defaultHttpChannelCount) -#endif // QT_NO_SSL + || type == QHttpNetworkConnection::ConnectionTypeSPDY +#endif + ? 1 : defaultHttpChannelCount) + , channelCount(defaultHttpChannelCount) #ifndef QT_NO_NETWORKPROXY , networkProxy(QNetworkProxy::NoProxy) #endif , preConnectRequests(0) , connectionType(type) { + // We allocate all 6 channels even if it's SPDY or HTTP/2 enabled + // connection: in case the protocol negotiation via NPN/ALPN fails, + // we will have normally working HTTP/1.1. + Q_ASSERT(channelCount >= activeChannelCount); channels = new QHttpNetworkConnectionChannel[channelCount]; } -QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, +QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QHttpNetworkConnection::ConnectionType type) : state(RunningState), networkLayerState(Unknown), hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true), - channelCount(channelCount) + activeChannelCount(connectionCount), channelCount(connectionCount) #ifndef QT_NO_NETWORKPROXY , networkProxy(QNetworkProxy::NoProxy) #endif @@ -147,7 +151,7 @@ void QHttpNetworkConnectionPrivate::pauseConnection() state = PausedState; // Disable all socket notifiers - for (int i = 0; i < channelCount; i++) { + for (int i = 0; i < activeChannelCount; i++) { if (channels[i].socket) { #ifndef QT_NO_SSL if (encrypt) @@ -163,7 +167,7 @@ void QHttpNetworkConnectionPrivate::resumeConnection() { state = RunningState; // Enable all socket notifiers - for (int i = 0; i < channelCount; i++) { + for (int i = 0; i < activeChannelCount; i++) { if (channels[i].socket) { #ifndef QT_NO_SSL if (encrypt) @@ -184,7 +188,7 @@ void QHttpNetworkConnectionPrivate::resumeConnection() int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const { - for (int i = 0; i < channelCount; ++i) + for (int i = 0; i < activeChannelCount; ++i) if (channels[i].socket == socket) return i; @@ -210,7 +214,7 @@ bool QHttpNetworkConnectionPrivate::shouldEmitChannelError(QAbstractSocket *sock channels[otherSocket].ensureConnection(); } - if (channelCount == 1) { + if (activeChannelCount < channelCount) { if (networkLayerState == HostLookupPending || networkLayerState == IPv4or6) networkLayerState = QHttpNetworkConnectionPrivate::Unknown; channels[0].close(); @@ -405,7 +409,7 @@ void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthentica // select another channel QAuthenticator* otherAuth = 0; - for (int i = 0; i < channelCount; ++i) { + for (int i = 0; i < activeChannelCount; ++i) { if (i == fromChannel) continue; if (isProxy) @@ -886,7 +890,7 @@ void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply) Q_Q(QHttpNetworkConnection); // check if the reply is currently being processed or it is pipelined in - for (int i = 0; i < channelCount; ++i) { + for (int i = 0; i < activeChannelCount; ++i) { // is the reply associated the currently processing of this channel? if (channels[i].reply == reply) { channels[i].reply = 0; @@ -989,7 +993,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() return; //resend the necessary ones. - for (int i = 0; i < channelCount; ++i) { + for (int i = 0; i < activeChannelCount; ++i) { if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) { channels[i].resendCurrent = false; @@ -1009,7 +1013,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() return; // try to get a free AND connected socket - for (int i = 0; i < channelCount; ++i) { + for (int i = 0; i < activeChannelCount; ++i) { if (channels[i].socket) { if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) { if (dequeueRequest(channels[i].socket)) @@ -1047,7 +1051,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() // return fast if there is nothing to pipeline if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) return; - for (int i = 0; i < channelCount; i++) + for (int i = 0; i < activeChannelCount; i++) if (channels[i].socket && channels[i].socket->state() == QAbstractSocket::ConnectedState) fillPipeline(channels[i].socket); @@ -1063,7 +1067,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() int normalRequests = queuedRequests - preConnectRequests; neededOpenChannels = qMax(normalRequests, preConnectRequests); } - for (int i = 0; i < channelCount && neededOpenChannels > 0; ++i) { + for (int i = 0; i < activeChannelCount && neededOpenChannels > 0; ++i) { bool connectChannel = false; if (channels[i].socket) { if ((channels[i].socket->state() == QAbstractSocket::ConnectingState) @@ -1093,7 +1097,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() void QHttpNetworkConnectionPrivate::readMoreLater(QHttpNetworkReply *reply) { - for (int i = 0 ; i < channelCount; ++i) { + for (int i = 0 ; i < activeChannelCount; ++i) { if (channels[i].reply == reply) { // emulate a readyRead() from the socket QMetaObject::invokeMethod(&channels[i], "_q_readyRead", Qt::QueuedConnection); @@ -1212,7 +1216,7 @@ void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(const QHostInfo &info) // connection will then be disconnected. void QHttpNetworkConnectionPrivate::startNetworkLayerStateLookup() { - if (channelCount > 1) { + if (activeChannelCount > 1) { // At this time all channels should be unconnected. Q_ASSERT(!channels[0].isSocketBusy()); Q_ASSERT(!channels[1].isSocketBusy()); @@ -1250,7 +1254,7 @@ void QHttpNetworkConnectionPrivate::startNetworkLayerStateLookup() void QHttpNetworkConnectionPrivate::networkLayerDetected(QAbstractSocket::NetworkLayerProtocol protocol) { - for (int i = 0 ; i < channelCount; ++i) { + for (int i = 0 ; i < activeChannelCount; ++i) { if ((channels[i].networkLayerPreference != protocol) && (channels[i].state == QHttpNetworkConnectionChannel::ConnectingState)) { channels[i].close(); } @@ -1347,7 +1351,7 @@ void QHttpNetworkConnection::setCacheProxy(const QNetworkProxy &networkProxy) d->networkProxy = networkProxy; // update the authenticator if (!d->networkProxy.user().isEmpty()) { - for (int i = 0; i < d->channelCount; ++i) { + for (int i = 0; i < d->activeChannelCount; ++i) { d->channels[i].proxyAuthenticator.setUser(d->networkProxy.user()); d->channels[i].proxyAuthenticator.setPassword(d->networkProxy.password()); } @@ -1363,7 +1367,7 @@ QNetworkProxy QHttpNetworkConnection::cacheProxy() const void QHttpNetworkConnection::setTransparentProxy(const QNetworkProxy &networkProxy) { Q_D(QHttpNetworkConnection); - for (int i = 0; i < d->channelCount; ++i) + for (int i = 0; i < d->activeChannelCount; ++i) d->channels[i].setProxy(networkProxy); } @@ -1395,7 +1399,7 @@ void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config return; // set the config on all channels - for (int i = 0; i < d->channelCount; ++i) + for (int i = 0; i < d->activeChannelCount; ++i) d->channels[i].setSslConfiguration(config); } @@ -1418,7 +1422,7 @@ void QHttpNetworkConnection::ignoreSslErrors(int channel) return; if (channel == -1) { // ignore for all channels - for (int i = 0; i < d->channelCount; ++i) { + for (int i = 0; i < d->activeChannelCount; ++i) { d->channels[i].ignoreSslErrors(); } @@ -1434,7 +1438,7 @@ void QHttpNetworkConnection::ignoreSslErrors(const QList &errors, int return; if (channel == -1) { // ignore for all channels - for (int i = 0; i < d->channelCount; ++i) { + for (int i = 0; i < d->activeChannelCount; ++i) { d->channels[i].ignoreSslErrors(errors); } diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h index 430c715717..3dd9bde9bd 100644 --- a/src/network/access/qhttpnetworkconnection_p.h +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -243,6 +243,9 @@ public: bool encrypt; bool delayIpv4; + // Number of channels we are trying to use at the moment: + int activeChannelCount; + // The total number of channels we reserved: const int channelCount; QTimer delayedConnectionTimer; QHttpNetworkConnectionChannel *channels; // parallel connections to the server diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 7fa19dc65b..a11fc7e807 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -1082,12 +1082,36 @@ void QHttpNetworkConnectionChannel::_q_encrypted() } } Q_FALLTHROUGH(); - case QSslConfiguration::NextProtocolNegotiationNone: + case QSslConfiguration::NextProtocolNegotiationNone: { protocolHandler.reset(new QHttpProtocolHandler(this)); + + QList 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. + if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) + protocols.removeAll(QSslConfiguration::ALPNProtocolHTTP2); + else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY) + protocols.removeAll(QSslConfiguration::NextProtocolSpdy3_0); + + if (nProtocols > protocols.size()) { + 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->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP); - // re-queue requests from SPDY queue to HTTP queue, if any - requeueSpdyRequests(); + // We use only one channel for SPDY or HTTP/2, but normally six for + // HTTP/1.1 - let's restore this number to the reserved number of + // channels: + if (connection->d_func()->activeChannelCount < connection->d_func()->channelCount) { + connection->d_func()->activeChannelCount = connection->d_func()->channelCount; + // re-queue requests from SPDY queue to HTTP queue, if any + requeueSpdyRequests(); + } break; + } default: emitFinishedWithError(QNetworkReply::SslHandshakeFailedError, "detected unknown Next Protocol Negotiation protocol"); -- cgit v1.2.3 From dfb6850b916271333d2bf1b244f5f3dc8a89fcb1 Mon Sep 17 00:00:00 2001 From: Daiwei Li Date: Tue, 15 Sep 2015 20:06:08 -0700 Subject: Android: Bearer jar shouldn't be a dependency if disabled Task-number: QTBUG-59264 Change-Id: I960d5bff902b06ca5dda447fd60002a756a11e51 Reviewed-by: Lars Knoll --- src/network/network.pro | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'src/network') diff --git a/src/network/network.pro b/src/network/network.pro index 75105bd681..98fbf82275 100644 --- a/src/network/network.pro +++ b/src/network/network.pro @@ -22,16 +22,18 @@ include(ssl/ssl.pri) QMAKE_LIBS += $$QMAKE_LIBS_NETWORK -ANDROID_BUNDLED_JAR_DEPENDENCIES = \ - jar/QtAndroidBearer-bundled.jar -ANDROID_JAR_DEPENDENCIES = \ - jar/QtAndroidBearer.jar -ANDROID_LIB_DEPENDENCIES = \ - plugins/bearer/libqandroidbearer.so -MODULE_PLUGIN_TYPES = \ - bearer -ANDROID_PERMISSIONS += \ - android.permission.ACCESS_NETWORK_STATE +qtConfig(bearermanagement) { + ANDROID_BUNDLED_JAR_DEPENDENCIES = \ + jar/QtAndroidBearer-bundled.jar + ANDROID_JAR_DEPENDENCIES = \ + jar/QtAndroidBearer.jar + ANDROID_LIB_DEPENDENCIES = \ + plugins/bearer/libqandroidbearer.so + MODULE_PLUGIN_TYPES = \ + bearer + ANDROID_PERMISSIONS += \ + android.permission.ACCESS_NETWORK_STATE +} MODULE_WINRT_CAPABILITIES = \ internetClient \ -- cgit v1.2.3 From d82d2f67161ed12d94edcc79914ec74df4cd40d3 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Thu, 2 Mar 2017 17:41:06 +0100 Subject: QSslSocket: fix connection to a international domain name RFC6125 section 6.4.2 specify we need to convert the IDN to ascii before comparison. Note that we don't need to toLower anymore because toAce takes care of it. Section 7.2 recommands that we dod not attempt to check for wildcard character embedded within the A-labels or U-labels of an internationalized domain name. So we reject names that contiains a '*' but starts with 'xn--'. Change-Id: Ib0830520a1f82bbf9fd11818718277a479527ee3 Reviewed-by: Timur Pocheptsov --- src/network/ssl/qsslsocket.cpp | 25 +++++++++++++++++++------ src/network/ssl/qsslsocket_p.h | 3 ++- 2 files changed, 21 insertions(+), 7 deletions(-) (limited to 'src/network') diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 2fc779b257..166907780b 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -326,6 +326,7 @@ #include #include #include +#include #include #include #include @@ -2673,31 +2674,35 @@ QSharedPointer QSslSocketPrivate::sslContext(QSslSocket *socket) bool QSslSocketPrivate::isMatchingHostname(const QSslCertificate &cert, const QString &peerName) { - const QString lowerPeerName = peerName.toLower(); + const QString lowerPeerName = QString::fromLatin1(QUrl::toAce(peerName)); const QStringList commonNames = cert.subjectInfo(QSslCertificate::CommonName); for (const QString &commonName : commonNames) { - if (isMatchingHostname(commonName.toLower(), lowerPeerName)) + if (isMatchingHostname(commonName, lowerPeerName)) return true; } const auto subjectAlternativeNames = cert.subjectAlternativeNames(); const auto altNames = subjectAlternativeNames.equal_range(QSsl::DnsEntry); for (auto it = altNames.first; it != altNames.second; ++it) { - if (isMatchingHostname(it->toLower(), lowerPeerName)) + if (isMatchingHostname(*it, lowerPeerName)) return true; } return false; } +/*! \internal + Checks if the certificate's name \a cn matches the \a hostname. + \a hostname must be normalized in ASCII-Compatible Encoding, but \a cn is not normalized + */ bool QSslSocketPrivate::isMatchingHostname(const QString &cn, const QString &hostname) { int wildcard = cn.indexOf(QLatin1Char('*')); // Check this is a wildcard cert, if not then just compare the strings if (wildcard < 0) - return cn == hostname; + return QLatin1String(QUrl::toAce(cn)) == hostname; int firstCnDot = cn.indexOf(QLatin1Char('.')); int secondCnDot = cn.indexOf(QLatin1Char('.'), firstCnDot+1); @@ -2714,13 +2719,21 @@ bool QSslSocketPrivate::isMatchingHostname(const QString &cn, const QString &hos if (cn.lastIndexOf(QLatin1Char('*')) != wildcard) return false; + // Reject wildcard character embedded within the A-labels or U-labels of an internationalized + // domain name (RFC6125 section 7.2) + if (cn.startsWith(QLatin1String("xn--"), Qt::CaseInsensitive)) + return false; + // Check characters preceding * (if any) match - if (wildcard && (hostname.leftRef(wildcard) != cn.leftRef(wildcard))) + if (wildcard && hostname.leftRef(wildcard).compare(cn.leftRef(wildcard), Qt::CaseInsensitive) != 0) return false; // Check characters following first . match - if (hostname.midRef(hostname.indexOf(QLatin1Char('.'))) != cn.midRef(firstCnDot)) + int hnDot = hostname.indexOf(QLatin1Char('.')); + if (hostname.midRef(hnDot + 1) != cn.midRef(firstCnDot + 1) + && hostname.midRef(hnDot + 1) != QLatin1String(QUrl::toAce(cn.mid(firstCnDot + 1)))) { return false; + } // Check if the hostname is an IP address, if so then wildcards are not allowed QHostAddress addr(hostname); diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h index cec61d07c1..aec3437422 100644 --- a/src/network/ssl/qsslsocket_p.h +++ b/src/network/ssl/qsslsocket_p.h @@ -151,7 +151,8 @@ public: QRegExp::PatternSyntax syntax); static void addDefaultCaCertificate(const QSslCertificate &cert); static void addDefaultCaCertificates(const QList &certs); - static bool isMatchingHostname(const QSslCertificate &cert, const QString &peerName); + Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QSslCertificate &cert, + const QString &peerName); Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QString &cn, const QString &hostname); #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) -- cgit v1.2.3