From 1de244ea65f1b40c488fe92b29170c1b1d447233 Mon Sep 17 00:00:00 2001 From: Peter Hartmann Date: Wed, 22 Jan 2014 14:54:21 +0100 Subject: network: add support for the SPDY protocol Currently the only supported SPDY version is 3.0. The feature needs to be enabled explicitly via QNetworkRequest::SpdyAllowedAttribute. Whether SPDY actually was used can be determined via QNetworkRequest::SpdyWasUsedAttribute from a QNetworkReply once it has been started (i.e. after the encrypted() signal has been received). Whether SPDY can be used will be determined during the SSL handshake through the TLS NPN extension (see separate commit). The following things from SPDY have not been enabled currently: * server push is not implemented, it has never been seen in the wild; in that case we just reject a stream pushed by the server, which is legit. * settings are not persisted across SPDY sessions. In practice this means that the server sends a small message upon session start telling us e.g. the number of concurrent connections. * SSL client certificates are not supported. Task-number: QTBUG-18714 [ChangeLog][QtNetwork] Added support for the SPDY protocol (version 3.0). Change-Id: I81bbe0495c24ed84e9cf8af3a9dbd63ca1e93d0d Reviewed-by: Richard J. Moore --- src/network/access/access.pri | 2 + src/network/access/qhttpnetworkconnection.cpp | 154 ++- src/network/access/qhttpnetworkconnection_p.h | 37 +- .../access/qhttpnetworkconnectionchannel.cpp | 130 +- .../access/qhttpnetworkconnectionchannel_p.h | 8 +- src/network/access/qhttpnetworkreply.cpp | 50 +- src/network/access/qhttpnetworkreply_p.h | 16 +- src/network/access/qhttpnetworkrequest.cpp | 16 +- src/network/access/qhttpnetworkrequest_p.h | 5 + src/network/access/qhttpthreaddelegate.cpp | 38 +- src/network/access/qhttpthreaddelegate_p.h | 4 +- src/network/access/qnetworkaccessmanager.cpp | 13 + src/network/access/qnetworkreplyhttpimpl.cpp | 20 +- src/network/access/qnetworkreplyhttpimpl_p.h | 7 +- src/network/access/qnetworkrequest.cpp | 11 + src/network/access/qnetworkrequest.h | 2 + src/network/access/qspdyprotocolhandler.cpp | 1272 ++++++++++++++++++++ src/network/access/qspdyprotocolhandler_p.h | 228 ++++ 18 files changed, 1931 insertions(+), 82 deletions(-) create mode 100644 src/network/access/qspdyprotocolhandler.cpp create mode 100644 src/network/access/qspdyprotocolhandler_p.h (limited to 'src/network/access') diff --git a/src/network/access/access.pri b/src/network/access/access.pri index 3017417da8..e829d52cbe 100644 --- a/src/network/access/access.pri +++ b/src/network/access/access.pri @@ -9,6 +9,7 @@ HEADERS += \ access/qhttpnetworkconnectionchannel_p.h \ access/qabstractprotocolhandler_p.h \ access/qhttpprotocolhandler_p.h \ + access/qspdyprotocolhandler_p.h \ access/qnetworkaccessauthenticationmanager_p.h \ access/qnetworkaccessmanager.h \ access/qnetworkaccessmanager_p.h \ @@ -47,6 +48,7 @@ SOURCES += \ access/qhttpnetworkconnectionchannel.cpp \ access/qabstractprotocolhandler.cpp \ access/qhttpprotocolhandler.cpp \ + access/qspdyprotocolhandler.cpp \ access/qnetworkaccessauthenticationmanager.cpp \ access/qnetworkaccessmanager.cpp \ access/qnetworkaccesscache.cpp \ diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 66c97f7485..6d568220e2 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -68,7 +68,7 @@ QT_BEGIN_NAMESPACE -const int QHttpNetworkConnectionPrivate::defaultChannelCount = 6; +const int QHttpNetworkConnectionPrivate::defaultHttpChannelCount = 6; // The pipeline length. So there will be 4 requests in flight. const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3; @@ -77,20 +77,29 @@ const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3; const int QHttpNetworkConnectionPrivate::defaultRePipelineLength = 2; -QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt) +QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName, + quint16 port, bool encrypt, + QHttpNetworkConnection::ConnectionType type) : state(RunningState), networkLayerState(Unknown), - hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true), - channelCount(defaultChannelCount) + hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true) +#ifndef QT_NO_SSL +, channelCount((type == QHttpNetworkConnection::ConnectionTypeSPDY) ? 1 : defaultHttpChannelCount) +#else +, channelCount(defaultHttpChannelCount) +#endif // QT_NO_SSL #ifndef QT_NO_NETWORKPROXY , networkProxy(QNetworkProxy::NoProxy) #endif , preConnectRequests(0) + , connectionType(type) { channels = new QHttpNetworkConnectionChannel[channelCount]; } -QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt) +QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, + quint16 port, bool encrypt, + QHttpNetworkConnection::ConnectionType type) : state(RunningState), networkLayerState(Unknown), hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true), channelCount(channelCount) @@ -98,6 +107,7 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCoun , networkProxy(QNetworkProxy::NoProxy) #endif , preConnectRequests(0) + , connectionType(type) { channels = new QHttpNetworkConnectionChannel[channelCount]; } @@ -546,15 +556,24 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor if (request.isPreConnect()) preConnectRequests++; - switch (request.priority()) { - case QHttpNetworkRequest::HighPriority: - highPriorityQueue.prepend(pair); - break; - case QHttpNetworkRequest::NormalPriority: - case QHttpNetworkRequest::LowPriority: - lowPriorityQueue.prepend(pair); - break; + if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP) { + switch (request.priority()) { + case QHttpNetworkRequest::HighPriority: + highPriorityQueue.prepend(pair); + break; + case QHttpNetworkRequest::NormalPriority: + case QHttpNetworkRequest::LowPriority: + lowPriorityQueue.prepend(pair); + break; + } } +#ifndef QT_NO_SSL + else { // SPDY + if (!pair.second->d_func()->requestIsPrepared) + prepareRequest(pair); + channels[0].spdyRequestsToSend.insertMulti(request.priority(), pair); + } +#endif // QT_NO_SSL // For Happy Eyeballs the networkLayerState is set to Unknown // untill we have started the first connection attempt. So no @@ -900,17 +919,39 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() // dequeue new ones - // return fast if there is nothing to do - if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) - return; - // try to get a free AND connected socket - for (int i = 0; i < channelCount; ++i) { - if (channels[i].socket) { - if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) { - if (dequeueRequest(channels[i].socket)) - channels[i].sendRequest(); + switch (connectionType) { + case QHttpNetworkConnection::ConnectionTypeHTTP: { + // return fast if there is nothing to do + if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) + return; + + // try to get a free AND connected socket + for (int i = 0; i < channelCount; ++i) { + if (channels[i].socket) { + if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) { + if (dequeueRequest(channels[i].socket)) + channels[i].sendRequest(); + } } } + break; + } + case QHttpNetworkConnection::ConnectionTypeSPDY: { +#ifndef QT_NO_SSL + if (channels[0].spdyRequestsToSend.isEmpty()) + return; + + if (networkLayerState == IPv4) + channels[0].networkLayerPreference = QAbstractSocket::IPv4Protocol; + else if (networkLayerState == IPv6) + channels[0].networkLayerPreference = QAbstractSocket::IPv6Protocol; + channels[0].ensureConnection(); + if (channels[0].socket && channels[0].socket->state() == QAbstractSocket::ConnectedState + && !channels[0].pendingEncrypt) + channels[0].sendRequest(); +#endif // QT_NO_SSL + break; + } } // try to push more into all sockets @@ -1059,7 +1100,19 @@ void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(QHostInfo info) if (dequeueRequest(channels[0].socket)) { emitReplyError(channels[0].socket, channels[0].reply, QNetworkReply::HostNotFoundError); networkLayerState = QHttpNetworkConnectionPrivate::Unknown; - } else { + } +#ifndef QT_NO_SSL + else if (connectionType == QHttpNetworkConnection::ConnectionTypeSPDY) { + QList spdyPairs = channels[0].spdyRequestsToSend.values(); + for (int a = 0; a < spdyPairs.count(); ++a) { + // emit error for all replies + QHttpNetworkReply *currentReply = spdyPairs.at(a).second; + Q_ASSERT(currentReply); + emitReplyError(channels[0].socket, currentReply, QNetworkReply::HostNotFoundError); + } + } +#endif // QT_NO_SSL + else { // Should not happen qWarning() << "QHttpNetworkConnectionPrivate::_q_hostLookupFinished could not dequeu request"; networkLayerState = QHttpNetworkConnectionPrivate::Unknown; @@ -1127,31 +1180,41 @@ void QHttpNetworkConnectionPrivate::_q_connectDelayedChannel() } #ifndef QT_NO_BEARERMANAGEMENT -QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent, QSharedPointer networkSession) - : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent) +QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, + QHttpNetworkConnection::ConnectionType connectionType, + QObject *parent, QSharedPointer networkSession) + : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt, connectionType)), parent) { Q_D(QHttpNetworkConnection); d->networkSession = networkSession; d->init(); } -QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent, QSharedPointer networkSession) - : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt)), parent) +QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, + quint16 port, bool encrypt, QObject *parent, + QSharedPointer networkSession, + QHttpNetworkConnection::ConnectionType connectionType) + : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt, + connectionType)), parent) { Q_D(QHttpNetworkConnection); d->networkSession = networkSession; d->init(); } #else -QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent) - : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent) +QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent, + QHttpNetworkConnection::ConnectionType connectionType) + : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt , connectionType)), parent) { Q_D(QHttpNetworkConnection); d->init(); } -QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent) - : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt)), parent) +QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, + quint16 port, bool encrypt, QObject *parent, + QHttpNetworkConnection::ConnectionType connectionType) + : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt, + connectionType)), parent) { Q_D(QHttpNetworkConnection); d->init(); @@ -1225,6 +1288,17 @@ QNetworkProxy QHttpNetworkConnection::transparentProxy() const } #endif +QHttpNetworkConnection::ConnectionType QHttpNetworkConnection::connectionType() +{ + Q_D(QHttpNetworkConnection); + return d->connectionType; +} + +void QHttpNetworkConnection::setConnectionType(ConnectionType type) +{ + Q_D(QHttpNetworkConnection); + d->connectionType = type; +} // SSL support below #ifndef QT_NO_SSL @@ -1299,7 +1373,23 @@ void QHttpNetworkConnectionPrivate::emitProxyAuthenticationRequired(const QHttpN // Also pause the connection because socket notifiers may fire while an user // dialog is displaying pauseConnection(); - emit chan->reply->proxyAuthenticationRequired(proxy, auth); + QHttpNetworkReply *reply; +#ifndef QT_NO_SSL + if (connectionType == QHttpNetworkConnection::ConnectionTypeSPDY) { + // we choose the reply to emit the proxyAuth signal from somewhat arbitrarily, + // but that does not matter because the signal will ultimately be emitted + // by the QNetworkAccessManager. + Q_ASSERT(chan->spdyRequestsToSend.count() > 0); + reply = chan->spdyRequestsToSend.values().first().second; + } else { // HTTP +#endif // QT_NO_SSL + reply = chan->reply; +#ifndef QT_NO_SSL + } +#endif // QT_NO_SSL + + Q_ASSERT(reply); + emit reply->proxyAuthenticationRequired(proxy, auth); resumeConnection(); int i = indexOf(chan->socket); copyCredentials(i, auth, true); diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h index 526326c3fd..9d4257e217 100644 --- a/src/network/access/qhttpnetworkconnection_p.h +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -94,12 +94,27 @@ class Q_AUTOTEST_EXPORT QHttpNetworkConnection : public QObject Q_OBJECT public: + enum ConnectionType { + ConnectionTypeHTTP, + ConnectionTypeSPDY + }; + #ifndef QT_NO_BEARERMANAGEMENT - explicit QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0, QSharedPointer networkSession = QSharedPointer()); - QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0, QSharedPointer networkSession = QSharedPointer()); + explicit QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, + ConnectionType connectionType = ConnectionTypeHTTP, + QObject *parent = 0, QSharedPointer networkSession + = QSharedPointer()); + QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, + bool encrypt = false, QObject *parent = 0, + QSharedPointer networkSession = QSharedPointer(), + ConnectionType connectionType = ConnectionTypeHTTP); #else - explicit QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0); - QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0); + explicit QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, + QObject *parent = 0, + ConnectionType connectionType = ConnectionTypeHTTP); + QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, + bool encrypt = false, QObject *parent = 0, + ConnectionType connectionType = ConnectionTypeHTTP); #endif ~QHttpNetworkConnection(); @@ -123,6 +138,9 @@ public: QHttpNetworkConnectionChannel *channels() const; + ConnectionType connectionType(); + void setConnectionType(ConnectionType type); + #ifndef QT_NO_SSL void setSslConfiguration(const QSslConfiguration &config); void ignoreSslErrors(int channel = -1); @@ -140,6 +158,7 @@ private: friend class QHttpNetworkReplyPrivate; friend class QHttpNetworkConnectionChannel; friend class QHttpProtocolHandler; + friend class QSpdyProtocolHandler; Q_PRIVATE_SLOT(d_func(), void _q_startNextRequest()) Q_PRIVATE_SLOT(d_func(), void _q_hostLookupFinished(QHostInfo)) @@ -155,7 +174,7 @@ class QHttpNetworkConnectionPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QHttpNetworkConnection) public: - static const int defaultChannelCount; + static const int defaultHttpChannelCount; static const int defaultPipelineLength; static const int defaultRePipelineLength; @@ -172,8 +191,10 @@ public: IPv4or6 }; - QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt); - QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt); + QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt, + QHttpNetworkConnection::ConnectionType type); + QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt, + QHttpNetworkConnection::ConnectionType type); ~QHttpNetworkConnectionPrivate(); void init(); @@ -245,6 +266,8 @@ public: int preConnectRequests; + QHttpNetworkConnection::ConnectionType connectionType; + #ifndef QT_NO_SSL QSharedPointer sslContext; #endif diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 6f06c18732..fb40958178 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -50,6 +50,7 @@ #ifndef QT_NO_HTTP #include +#include #ifndef QT_NO_SSL # include @@ -166,9 +167,12 @@ void QHttpNetworkConnectionChannel::init() if (!sslConfiguration.isNull()) sslSocket->setSslConfiguration(sslConfiguration); + } else { +#endif // QT_NO_SSL + protocolHandler.reset(new QHttpProtocolHandler(this)); +#ifndef QT_NO_SSL } #endif - protocolHandler.reset(new QHttpProtocolHandler(this)); #ifndef QT_NO_NETWORKPROXY if (proxy.type() != QNetworkProxy::NoProxy) @@ -879,6 +883,18 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket if (protocolHandler) protocolHandler->setReply(0); } +#ifndef QT_NO_SSL + else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY) { + QList spdyPairs = spdyRequestsToSend.values(); + for (int a = 0; a < spdyPairs.count(); ++a) { + // emit error for all replies + QHttpNetworkReply *currentReply = spdyPairs.at(a).second; + Q_ASSERT(currentReply); + emit currentReply->finishedWithError(errorCode, errorString); + } + } +#endif // QT_NO_SSL + // send the next request QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection); @@ -889,11 +905,19 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket #ifndef QT_NO_NETWORKPROXY void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth) { - // Need to dequeue the request before we can emit the error. - if (!reply) - connection->d_func()->dequeueRequest(socket); - if (reply) +#ifndef QT_NO_SSL + if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY) { connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth); + } else { // HTTP +#endif // QT_NO_SSL + // Need to dequeue the request before we can emit the error. + if (!reply) + connection->d_func()->dequeueRequest(socket); + if (reply) + connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth); +#ifndef QT_NO_SSL + } +#endif // QT_NO_SSL } #endif @@ -905,16 +929,85 @@ void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead() #ifndef QT_NO_SSL void QHttpNetworkConnectionChannel::_q_encrypted() { + QSslSocket *sslSocket = qobject_cast(socket); + Q_ASSERT(sslSocket); + + if (!protocolHandler) { + switch (sslSocket->sslConfiguration().nextProtocolNegotiationStatus()) { + case QSslConfiguration::NextProtocolNegotiationNegotiated: { + QByteArray nextProtocol = sslSocket->sslConfiguration().nextNegotiatedProtocol(); + if (nextProtocol == QSslConfiguration::NextProtocolHttp1_1) { + // fall through to create a QHttpProtocolHandler + } else if (nextProtocol == QSslConfiguration::NextProtocolSpdy3_0) { + protocolHandler.reset(new QSpdyProtocolHandler(this)); + connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeSPDY); + // no need to re-queue requests, if SPDY was enabled on the request it + // has gone to the SPDY queue already + break; + } else { + emitFinishedWithError(QNetworkReply::SslHandshakeFailedError, + "detected unknown Next Protocol Negotiation protocol"); + break; + } + } + case QSslConfiguration::NextProtocolNegotiationNone: + protocolHandler.reset(new QHttpProtocolHandler(this)); + connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP); + // re-queue requests from SPDY queue to HTTP queue, if any + requeueSpdyRequests(); + break; + case QSslConfiguration::NextProtocolNegotiationUnsupported: + emitFinishedWithError(QNetworkReply::SslHandshakeFailedError, + "chosen Next Protocol Negotiation value unsupported"); + break; + default: + emitFinishedWithError(QNetworkReply::SslHandshakeFailedError, + "detected unknown Next Protocol Negotiation protocol"); + } + } + if (!socket) return; // ### error state = QHttpNetworkConnectionChannel::IdleState; pendingEncrypt = false; - if (!reply) - connection->d_func()->dequeueRequest(socket); - if (reply) - emit reply->encrypted(); + + if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY) { + // we call setSpdyWasUsed(true) on the replies in the SPDY handler when the request is sent + if (spdyRequestsToSend.count() > 0) + // wait for data from the server first (e.g. initial window, max concurrent requests) + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + } else { // HTTP + if (!reply) + connection->d_func()->dequeueRequest(socket); + if (reply) { + reply->setSpdyWasUsed(false); + emit reply->encrypted(); + } + if (reply) + sendRequest(); + } +} + +void QHttpNetworkConnectionChannel::requeueSpdyRequests() +{ + QList spdyPairs = spdyRequestsToSend.values(); + for (int a = 0; a < spdyPairs.count(); ++a) { + connection->d_func()->requeueRequest(spdyPairs.at(a)); + } + spdyRequestsToSend.clear(); +} + +void QHttpNetworkConnectionChannel::emitFinishedWithError(QNetworkReply::NetworkError error, + const char *message) +{ if (reply) - sendRequest(); + emit reply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message)); + QList spdyPairs = spdyRequestsToSend.values(); + for (int a = 0; a < spdyPairs.count(); ++a) { + QHttpNetworkReply *currentReply = spdyPairs.at(a).second; + Q_ASSERT(currentReply); + emit currentReply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message)); + } } void QHttpNetworkConnectionChannel::_q_sslErrors(const QList &errors) @@ -927,8 +1020,21 @@ void QHttpNetworkConnectionChannel::_q_sslErrors(const QList &errors) connection->d_func()->pauseConnection(); if (pendingEncrypt && !reply) connection->d_func()->dequeueRequest(socket); - if (reply) - emit reply->sslErrors(errors); + if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP) { + if (reply) + emit reply->sslErrors(errors); + } +#ifndef QT_NO_SSL + else { // SPDY + QList spdyPairs = spdyRequestsToSend.values(); + for (int a = 0; a < spdyPairs.count(); ++a) { + // emit SSL errors for all replies + QHttpNetworkReply *currentReply = spdyPairs.at(a).second; + Q_ASSERT(currentReply); + emit currentReply->sslErrors(errors); + } + } +#endif // QT_NO_SSL connection->d_func()->resumeConnection(); } diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h index 7230eb2543..450b6fc419 100644 --- a/src/network/access/qhttpnetworkconnectionchannel_p.h +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h @@ -104,8 +104,8 @@ public: bool ssl; bool isInitialized; ChannelState state; - QHttpNetworkRequest request; // current request - QHttpNetworkReply *reply; // current reply for this request + QHttpNetworkRequest request; // current request, only used for HTTP + QHttpNetworkReply *reply; // current reply for this request, only used for HTTP qint64 written; qint64 bytesTotal; bool resendCurrent; @@ -123,9 +123,13 @@ public: bool ignoreAllSslErrors; QList ignoreSslErrorsList; QSslConfiguration sslConfiguration; + QMultiMap spdyRequestsToSend; // sorted by priority void ignoreSslErrors(); void ignoreSslErrors(const QList &errors); void setSslConfiguration(const QSslConfiguration &config); + void requeueSpdyRequests(); // when we wanted SPDY but got HTTP + // to emit the signal for all in-flight replies: + void emitFinishedWithError(QNetworkReply::NetworkError error, const char *message); #endif #ifndef QT_NO_BEARERMANAGEMENT QSharedPointer networkSession; diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp index 1b9e1f5a53..2a7e6ed638 100644 --- a/src/network/access/qhttpnetworkreply.cpp +++ b/src/network/access/qhttpnetworkreply.cpp @@ -265,6 +265,16 @@ bool QHttpNetworkReply::isPipeliningUsed() const return d_func()->pipeliningUsed; } +bool QHttpNetworkReply::isSpdyUsed() const +{ + return d_func()->spdyUsed; +} + +void QHttpNetworkReply::setSpdyWasUsed(bool spdy) +{ + d_func()->spdyUsed = spdy; +} + QHttpNetworkConnection* QHttpNetworkReply::connection() { return d_func()->connection; @@ -281,9 +291,15 @@ QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) connectionCloseEnabled(true), forceConnectionCloseEnabled(false), lastChunkRead(false), - currentChunkSize(0), currentChunkRead(0), readBufferMaxSize(0), connection(0), + currentChunkSize(0), currentChunkRead(0), readBufferMaxSize(0), + windowSizeDownload(65536), // 64K initial window size according to SPDY standard + windowSizeUpload(65536), // 64K initial window size according to SPDY standard + currentlyReceivedDataInWindow(0), + currentlyUploadedDataInWindow(0), + totallyUploadedData(0), + connection(0), autoDecompress(false), responseData(), requestIsPrepared(false) - ,pipeliningUsed(false), downstreamLimited(false) + ,pipeliningUsed(false), spdyUsed(false), downstreamLimited(false) ,userProvidedDownloadBuffer(0) #ifndef QT_NO_COMPRESS ,inflateStrm(0) @@ -550,15 +566,7 @@ qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket) // allocate inflate state if (!inflateStrm) inflateStrm = new z_stream; - inflateStrm->zalloc = Z_NULL; - inflateStrm->zfree = Z_NULL; - inflateStrm->opaque = Z_NULL; - inflateStrm->avail_in = 0; - inflateStrm->next_in = Z_NULL; - // "windowBits can also be greater than 15 for optional gzip decoding. - // Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection" - // http://www.zlib.net/manual.html - int ret = inflateInit2(inflateStrm, MAX_WBITS+32); + int ret = initializeInflateStream(); if (ret != Z_OK) return -1; } @@ -703,8 +711,28 @@ qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuff } #ifndef QT_NO_COMPRESS +int QHttpNetworkReplyPrivate::initializeInflateStream() +{ + inflateStrm->zalloc = Z_NULL; + inflateStrm->zfree = Z_NULL; + inflateStrm->opaque = Z_NULL; + inflateStrm->avail_in = 0; + inflateStrm->next_in = Z_NULL; + // "windowBits can also be greater than 15 for optional gzip decoding. + // Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection" + // http://www.zlib.net/manual.html + int ret = inflateInit2(inflateStrm, MAX_WBITS+32); + Q_ASSERT(ret == Z_OK); + return ret; +} + qint64 QHttpNetworkReplyPrivate::uncompressBodyData(QByteDataBuffer *in, QByteDataBuffer *out) { + if (!inflateStrm) { // happens when called from the SPDY protocol handler + inflateStrm = new z_stream; + initializeInflateStream(); + } + if (!inflateStrm) return -1; diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h index 583c3e426f..e15ace0072 100644 --- a/src/network/access/qhttpnetworkreply_p.h +++ b/src/network/access/qhttpnetworkreply_p.h @@ -132,6 +132,8 @@ public: bool isFinished() const; bool isPipeliningUsed() const; + bool isSpdyUsed() const; + void setSpdyWasUsed(bool spdy); QHttpNetworkConnection* connection(); @@ -165,6 +167,7 @@ private: friend class QHttpNetworkConnectionPrivate; friend class QHttpNetworkConnectionChannel; friend class QHttpProtocolHandler; + friend class QSpdyProtocolHandler; }; @@ -205,7 +208,11 @@ public: ReadingStatusState, ReadingHeaderState, ReadingDataState, - AllDoneState + AllDoneState, + SPDYSYNSent, + SPDYUploading, + SPDYHalfClosed, + SPDYClosed } state; QHttpNetworkRequest request; @@ -226,6 +233,11 @@ public: qint64 currentChunkSize; qint64 currentChunkRead; qint64 readBufferMaxSize; + qint32 windowSizeDownload; // only for SPDY + qint32 windowSizeUpload; // only for SPDY + qint32 currentlyReceivedDataInWindow; // only for SPDY + qint32 currentlyUploadedDataInWindow; // only for SPDY + qint64 totallyUploadedData; // only for SPDY QPointer connection; QPointer connectionChannel; @@ -236,12 +248,14 @@ public: bool requestIsPrepared; bool pipeliningUsed; + bool spdyUsed; bool downstreamLimited; char* userProvidedDownloadBuffer; #ifndef QT_NO_COMPRESS z_stream_s *inflateStrm; + int initializeInflateStream(); qint64 uncompressBodyData(QByteDataBuffer *in, QByteDataBuffer *out); #endif }; diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp index 3786f9b992..6e9f1216c3 100644 --- a/src/network/access/qhttpnetworkrequest.cpp +++ b/src/network/access/qhttpnetworkrequest.cpp @@ -49,8 +49,8 @@ QT_BEGIN_NAMESPACE QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Operation op, QHttpNetworkRequest::Priority pri, const QUrl &newUrl) : QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0), - autoDecompress(false), pipeliningAllowed(false), withCredentials(true), - preConnect(false) + autoDecompress(false), pipeliningAllowed(false), spdyAllowed(false), + withCredentials(true), preConnect(false) { } @@ -62,6 +62,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest uploadByteDevice = other.uploadByteDevice; autoDecompress = other.autoDecompress; pipeliningAllowed = other.pipeliningAllowed; + spdyAllowed = other.spdyAllowed; customVerb = other.customVerb; withCredentials = other.withCredentials; ssl = other.ssl; @@ -80,6 +81,7 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot && (uploadByteDevice == other.uploadByteDevice) && (autoDecompress == other.autoDecompress) && (pipeliningAllowed == other.pipeliningAllowed) + && (spdyAllowed == other.spdyAllowed) // we do not clear the customVerb in setOperation && (operation != QHttpNetworkRequest::Custom || (customVerb == other.customVerb)) && (withCredentials == other.withCredentials) @@ -299,6 +301,16 @@ void QHttpNetworkRequest::setPipeliningAllowed(bool b) d->pipeliningAllowed = b; } +bool QHttpNetworkRequest::isSPDYAllowed() const +{ + return d->spdyAllowed; +} + +void QHttpNetworkRequest::setSPDYAllowed(bool b) +{ + d->spdyAllowed = b; +} + bool QHttpNetworkRequest::withCredentials() const { return d->withCredentials; diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h index f224f7329d..09847d715c 100644 --- a/src/network/access/qhttpnetworkrequest_p.h +++ b/src/network/access/qhttpnetworkrequest_p.h @@ -114,6 +114,9 @@ public: bool isPipeliningAllowed() const; void setPipeliningAllowed(bool b); + bool isSPDYAllowed() const; + void setSPDYAllowed(bool b); + bool withCredentials() const; void setWithCredentials(bool b); @@ -135,6 +138,7 @@ private: friend class QHttpNetworkConnectionPrivate; friend class QHttpNetworkConnectionChannel; friend class QHttpProtocolHandler; + friend class QSpdyProtocolHandler; }; class QHttpNetworkRequestPrivate : public QHttpNetworkHeaderPrivate @@ -154,6 +158,7 @@ public: mutable QNonContiguousByteDevice* uploadByteDevice; bool autoDecompress; bool pipeliningAllowed; + bool spdyAllowed; bool withCredentials; bool ssl; bool preConnect; diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp index b7e8bb3f72..e03dcb8ead 100644 --- a/src/network/access/qhttpthreaddelegate.cpp +++ b/src/network/access/qhttpthreaddelegate.cpp @@ -180,11 +180,15 @@ class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection, // Q_OBJECT public: #ifdef QT_NO_BEARERMANAGEMENT - QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt) - : QHttpNetworkConnection(hostName, port, encrypt) + QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt, + QHttpNetworkConnection::ConnectionType connectionType) + : QHttpNetworkConnection(hostName, port, encrypt, connectionType) #else - QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt, QSharedPointer networkSession) - : QHttpNetworkConnection(hostName, port, encrypt, /*parent=*/0, networkSession) + QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt, + QHttpNetworkConnection::ConnectionType connectionType, + QSharedPointer networkSession) + : QHttpNetworkConnection(hostName, port, encrypt, connectionType, /*parent=*/0, + networkSession) #endif { setExpires(true); @@ -230,6 +234,7 @@ QHttpThreadDelegate::QHttpThreadDelegate(QObject *parent) : , synchronous(false) , incomingStatusCode(0) , isPipeliningUsed(false) + , isSpdyUsed(false) , incomingContentLength(-1) , incomingErrorCode(QNetworkReply::NoError) , downloadBuffer(0) @@ -281,6 +286,19 @@ void QHttpThreadDelegate::startRequest() QUrl urlCopy = httpRequest.url(); urlCopy.setPort(urlCopy.port(ssl ? 443 : 80)); + QHttpNetworkConnection::ConnectionType connectionType + = QHttpNetworkConnection::ConnectionTypeHTTP; +#ifndef QT_NO_SSL + if (httpRequest.isSPDYAllowed() && ssl) { + connectionType = QHttpNetworkConnection::ConnectionTypeSPDY; + urlCopy.setScheme(QStringLiteral("spdy")); // to differentiate SPDY requests from HTTPS requests + QList nextProtocols; + nextProtocols << QSslConfiguration::NextProtocolSpdy3_0 + << QSslConfiguration::NextProtocolHttp1_1; + incomingSslConfiguration.setAllowedNextProtocols(nextProtocols); + } +#endif // QT_NO_SSL + #ifndef QT_NO_NETWORKPROXY if (transparentProxy.type() != QNetworkProxy::NoProxy) cacheKey = makeCacheKey(urlCopy, &transparentProxy); @@ -297,9 +315,12 @@ void QHttpThreadDelegate::startRequest() // no entry in cache; create an object // the http object is actually a QHttpNetworkConnection #ifdef QT_NO_BEARERMANAGEMENT - httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl); + httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl, + connectionType); #else - httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl, networkSession); + httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl, + connectionType, + networkSession); #endif #ifndef QT_NO_SSL // Set the QSslConfiguration from this QNetworkRequest. @@ -575,13 +596,15 @@ void QHttpThreadDelegate::headerChangedSlot() incomingReasonPhrase = httpReply->reasonPhrase(); isPipeliningUsed = httpReply->isPipeliningUsed(); incomingContentLength = httpReply->contentLength(); + isSpdyUsed = httpReply->isSpdyUsed(); emit downloadMetaData(incomingHeaders, incomingStatusCode, incomingReasonPhrase, isPipeliningUsed, downloadBuffer, - incomingContentLength); + incomingContentLength, + isSpdyUsed); } void QHttpThreadDelegate::synchronousHeaderChangedSlot() @@ -597,6 +620,7 @@ void QHttpThreadDelegate::synchronousHeaderChangedSlot() incomingStatusCode = httpReply->statusCode(); incomingReasonPhrase = httpReply->reasonPhrase(); isPipeliningUsed = httpReply->isPipeliningUsed(); + isSpdyUsed = httpReply->isSpdyUsed(); incomingContentLength = httpReply->contentLength(); } diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h index d9ef1a0a55..6e6d3b5797 100644 --- a/src/network/access/qhttpthreaddelegate_p.h +++ b/src/network/access/qhttpthreaddelegate_p.h @@ -111,6 +111,7 @@ public: int incomingStatusCode; QString incomingReasonPhrase; bool isPipeliningUsed; + bool isSpdyUsed; qint64 incomingContentLength; QNetworkReply::NetworkError incomingErrorCode; QString incomingErrorDetail; @@ -139,7 +140,8 @@ signals: void sslErrors(const QList &, bool *, QList *); void sslConfigurationChanged(const QSslConfiguration); #endif - void downloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64); + void downloadMetaData(QList >, int, QString, bool, + QSharedPointer, qint64, bool); void downloadProgress(qint64, qint64); void downloadData(QByteArray); void error(QNetworkReply::NetworkError, const QString); diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index c89419091f..473acc5f22 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -977,6 +977,12 @@ QNetworkAccessManager::NetworkAccessibility QNetworkAccessManager::networkAccess \a sslConfiguration. This function is useful to complete the TCP and SSL handshake to a host before the HTTPS request is made, resulting in a lower network latency. + \note Preconnecting a SPDY connection can be done by calling setAllowedNextProtocols() + on \a sslConfiguration with QSslConfiguration::NextProtocolSpdy3_0 contained in + the list of allowed protocols. When using SPDY, one single connection per host is + enough, i.e. calling this method multiple times per host will not result in faster + network transactions. + \note This function has no possibility to report errors. \sa connectToHost(), get(), post(), put(), deleteResource() @@ -991,6 +997,13 @@ void QNetworkAccessManager::connectToHostEncrypted(const QString &hostName, quin QNetworkRequest request(url); if (sslConfiguration != QSslConfiguration::defaultConfiguration()) request.setSslConfiguration(sslConfiguration); + + // There is no way to enable SPDY via a request, so we need to check + // the ssl configuration whether SPDY is allowed here. + if (sslConfiguration.allowedNextProtocols().contains( + QSslConfiguration::NextProtocolSpdy3_0)) + request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true); + get(request); } #endif diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 29d23bfd8f..de4c8d0964 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -752,6 +752,9 @@ void QNetworkReplyHttpImplPrivate::postRequest() if (request.attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true) httpRequest.setPipeliningAllowed(true); + if (request.attribute(QNetworkRequest::SpdyAllowedAttribute).toBool() == true) + httpRequest.setSPDYAllowed(true); + if (static_cast (request.attribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual) @@ -811,8 +814,12 @@ void QNetworkReplyHttpImplPrivate::postRequest() QObject::connect(delegate, SIGNAL(downloadFinished()), q, SLOT(replyFinished()), Qt::QueuedConnection); - QObject::connect(delegate, SIGNAL(downloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64)), - q, SLOT(replyDownloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64)), + QObject::connect(delegate, SIGNAL(downloadMetaData(QList >, + int, QString, bool, + QSharedPointer, qint64, bool)), + q, SLOT(replyDownloadMetaData(QList >, + int, QString, bool, + QSharedPointer, qint64, bool)), Qt::QueuedConnection); QObject::connect(delegate, SIGNAL(downloadProgress(qint64,qint64)), q, SLOT(replyDownloadProgressSlot(qint64,qint64)), @@ -907,7 +914,8 @@ void QNetworkReplyHttpImplPrivate::postRequest() delegate->incomingReasonPhrase, delegate->isPipeliningUsed, QSharedPointer(), - delegate->incomingContentLength); + delegate->incomingContentLength, + delegate->isSpdyUsed); replyDownloadData(delegate->synchronousDownloadData); httpError(delegate->incomingErrorCode, delegate->incomingErrorDetail); } else { @@ -917,7 +925,8 @@ void QNetworkReplyHttpImplPrivate::postRequest() delegate->incomingReasonPhrase, delegate->isPipeliningUsed, QSharedPointer(), - delegate->incomingContentLength); + delegate->incomingContentLength, + delegate->isSpdyUsed); replyDownloadData(delegate->synchronousDownloadData); } @@ -1074,7 +1083,7 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData (QList > hm, int sc,QString rp,bool pu, QSharedPointer db, - qint64 contentLength) + qint64 contentLength, bool spdyWasUsed) { Q_Q(QNetworkReplyHttpImpl); Q_UNUSED(contentLength); @@ -1091,6 +1100,7 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData } q->setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, pu); + q->setAttribute(QNetworkRequest::SpdyWasUsedAttribute, spdyWasUsed); // reconstruct the HTTP header QList > headerMap = hm; diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h index 15cc0ec476..aa2d6f0ec9 100644 --- a/src/network/access/qnetworkreplyhttpimpl_p.h +++ b/src/network/access/qnetworkreplyhttpimpl_p.h @@ -111,7 +111,9 @@ public: // From reply Q_PRIVATE_SLOT(d_func(), void replyDownloadData(QByteArray)) Q_PRIVATE_SLOT(d_func(), void replyFinished()) - Q_PRIVATE_SLOT(d_func(), void replyDownloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64)) + Q_PRIVATE_SLOT(d_func(), void replyDownloadMetaData(QList >, + int, QString, bool, QSharedPointer, + qint64, bool)) Q_PRIVATE_SLOT(d_func(), void replyDownloadProgressSlot(qint64,qint64)) Q_PRIVATE_SLOT(d_func(), void httpAuthenticationRequired(const QHttpNetworkRequest &, QAuthenticator *)) Q_PRIVATE_SLOT(d_func(), void httpError(QNetworkReply::NetworkError, const QString &)) @@ -276,7 +278,8 @@ public: // From HTTP thread: void replyDownloadData(QByteArray); void replyFinished(); - void replyDownloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64); + void replyDownloadMetaData(QList >, int, QString, bool, + QSharedPointer, qint64, bool); void replyDownloadProgressSlot(qint64,qint64); void httpAuthenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *auth); void httpError(QNetworkReply::NetworkError error, const QString &errorString); diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index 65fd693771..aa1102f9bf 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -246,6 +246,17 @@ QT_BEGIN_NAMESPACE The QNetworkSession ConnectInBackground property will be set according to this attribute. + \value SpdyAllowedAttribute + Requests only, type: QMetaType::Bool (default: false) + Indicates whether the QNetworkAccessManager code is + allowed to use SPDY with this request. This applies only + to SSL requests, and depends on the server supporting SPDY. + + \value SpdyWasUsedAttribute + Replies only, type: QMetaType::Bool + Indicates whether SPDY was used for receiving + this reply. + \value User Special type. Additional information can be passed in QVariants with types ranging from User to UserMax. The default diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index 1512c6dadd..27b02a89ef 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -86,6 +86,8 @@ public: DownloadBufferAttribute, // internal SynchronousRequestAttribute, // internal BackgroundRequestAttribute, + SpdyAllowedAttribute, + SpdyWasUsedAttribute, User = 1000, UserMax = 32767 diff --git a/src/network/access/qspdyprotocolhandler.cpp b/src/network/access/qspdyprotocolhandler.cpp new file mode 100644 index 0000000000..f12b9535fe --- /dev/null +++ b/src/network/access/qspdyprotocolhandler.cpp @@ -0,0 +1,1272 @@ +/**************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +#if !defined(QT_NO_HTTP) && !defined(QT_NO_SSL) + +QT_BEGIN_NAMESPACE + +static const char spdyDictionary[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // ....opti + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // ons....h + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // ead....p + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // ost....p + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // ut....de + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // lete.... + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // trace... + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // .accept. + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t-charse + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t....acc + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ept-enco + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // ding.... + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // accept-l + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // anguage. + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t-ranges + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // ....age. + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // ...allow + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // ....auth + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // orizatio + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n....cac + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // he-contr + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // ol....co + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // nnection + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // ....cont + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // ent-base + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // ....cont + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ent-enco + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // ding.... + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // content- + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // language + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // ....cont + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // ent-leng + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // th....co + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // ntent-lo + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // cation.. + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t-md5... + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // .content + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // -range.. + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t-type.. + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // ..date.. + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // ..etag.. + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // ..expect + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // ....expi + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // res....f + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // rom....h + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // ost....i + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f-match. + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // ...if-mo + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // dified-s + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // ince.... + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // if-none- + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // match... + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // .if-rang + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e....if- + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // unmodifi + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // ed-since + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // ....last + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // -modifie + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d....loc + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // ation... + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // .max-for + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // wards... + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // .pragma. + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // ...proxy + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // -authent + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // icate... + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // .proxy-a + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // uthoriza + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // tion.... + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // range... + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // .referer + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // ....retr + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y-after. + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // ...serve + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r....te. + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // ...trail + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // er....tr + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // ansfer-e + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // ncoding. + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // ...upgra + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // de....us + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // er-agent + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // ....vary + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // ....via. + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // ...warni + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // ng....ww + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w-authen + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // ticate.. + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // ..method + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // ....get. + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // ...statu + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s....200 + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // .OK....v + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // ersion.. + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // ..HTTP.1 + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // .1....ur + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l....pub + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // lic....s + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // et-cooki + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e....kee + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p-alive. + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // ...origi + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n1001012 + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 01202205 + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 20630030 + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 23033043 + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 05306307 + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 40240540 + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 64074084 + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 09410411 + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 41241341 + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 44154164 + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 17502504 + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 505203.N + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // on-Autho + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // ritative + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // .Informa + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // tion204. + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // No.Conte + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // nt301.Mo + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // ved.Perm + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // anently4 + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 00.Bad.R + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // equest40 + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1.Unauth + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // orized40 + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3.Forbid + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // den404.N + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // ot.Found + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 500.Inte + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // rnal.Ser + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // ver.Erro + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r501.Not + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // .Impleme + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // nted503. + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // Service. + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // Unavaila + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // bleJan.F + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // eb.Mar.A + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // pr.May.J + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // un.Jul.A + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // ug.Sept. + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // Oct.Nov. + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // Dec.00.0 + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0.00.Mon + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // ..Tue..W + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // ed..Thu. + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // .Fri..Sa + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t..Sun.. + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // GMTchunk + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // ed.text. + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // html.ima + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // ge.png.i + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // mage.jpg + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // .image.g + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // if.appli + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // ml.appli + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // html.xml + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // .text.pl + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // ain.text + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // .javascr + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // ipt.publ + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // icprivat + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // emax-age + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // .gzip.de + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // flate.sd + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // chcharse + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t.utf-8c + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // harset.i + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // so-8859- + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1.utf-.. + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // .enq.0. +}; + +// uncomment to debug +//static void printHex(const QByteArray &ba) +//{ +// QByteArray hex; +// QByteArray clearText; +// for (int a = 0; a < ba.count(); ++a) { +// QByteArray currentHexChar = QByteArray(1, ba.at(a)).toHex().rightJustified(2, ' '); +// QByteArray currentChar; +// if (ba.at(a) >= 32 && ba.at(a) < 126) { // if ASCII, print the letter +// currentChar = QByteArray(1, ba.at(a)); +// } else { +// currentChar = " "; +// } +// clearText.append(currentChar.rightJustified(2, ' ')); +// hex.append(currentHexChar); +// hex.append(' '); +// clearText.append(' '); +// } +// int chunkSize = 102; // 12 == 4 bytes per line +// for (int a = 0; a < hex.count(); a += chunkSize) { +// qDebug() << hex.mid(a, chunkSize); +// qDebug() << clearText.mid(a, chunkSize); +// } +//} + +QSpdyProtocolHandler::QSpdyProtocolHandler(QHttpNetworkConnectionChannel *channel) + : QObject(0), QAbstractProtocolHandler(channel), + m_nextStreamID(-1), + m_maxConcurrentStreams(100), // 100 is recommended in the SPDY RFC + m_initialWindowSize(0), + m_waitingForCompleteStream(false) +{ + m_inflateStream.zalloc = Z_NULL; + m_inflateStream.zfree = Z_NULL; + m_inflateStream.opaque = Z_NULL; + int zlibRet = inflateInit(&m_inflateStream); + Q_ASSERT(zlibRet == Z_OK); + + m_deflateStream.zalloc = Z_NULL; + m_deflateStream.zfree = Z_NULL; + m_deflateStream.opaque = Z_NULL; + + // Do actually not compress (i.e. compression level = 0) + // when sending the headers because of the CRIME attack + zlibRet = deflateInit(&m_deflateStream, /* compression level = */ 0); + Q_ASSERT(zlibRet == Z_OK); +} + +QSpdyProtocolHandler::~QSpdyProtocolHandler() +{ + deflateEnd(&m_deflateStream); + deflateEnd(&m_inflateStream); +} + +bool QSpdyProtocolHandler::sendRequest() +{ + Q_ASSERT(!m_reply); + + int maxPossibleRequests = m_maxConcurrentStreams - m_inFlightStreams.count(); + Q_ASSERT(maxPossibleRequests >= 0); + if (maxPossibleRequests == 0) + return true; // return early if max concurrent requests are exceeded + + m_channel->state = QHttpNetworkConnectionChannel::WritingState; + + // requests will be ordered by priority (see QMultiMap doc) + QList requests = m_channel->spdyRequestsToSend.values(); + QList priorities = m_channel->spdyRequestsToSend.keys(); + + int requestsToSend = qMin(requests.count(), maxPossibleRequests); + + for (int a = 0; a < requestsToSend; ++a) { + HttpMessagePair currentPair = requests.at(a); + QHttpNetworkRequest currentRequest = requests.at(a).first; + QHttpNetworkReply *currentReply = requests.at(a).second; + + currentReply->setSpdyWasUsed(true); + qint32 streamID = generateNextStreamID(); + + currentReply->setRequest(currentRequest); + currentReply->d_func()->connection = m_connection; + currentReply->d_func()->connectionChannel = m_channel; + m_inFlightStreams.insert(streamID, currentPair); + + sendSYN_STREAM(currentPair, streamID, /* associatedToStreamID = */ 0); + int requestsRemoved = m_channel->spdyRequestsToSend.remove( + priorities.at(a), currentPair); + Q_ASSERT(requestsRemoved == 1); + Q_UNUSED(requestsRemoved); // silence -Wunused-variable + } + m_channel->state = QHttpNetworkConnectionChannel::IdleState; + return true; +} + +void QSpdyProtocolHandler::_q_receiveReply() +{ + Q_ASSERT(m_socket); + + // only run when the QHttpNetworkConnection is not currently being destructed, e.g. + // this function is called from _q_disconnected which is called because + // of ~QHttpNetworkConnectionPrivate + if (!qobject_cast(m_connection)) { + return; + } + + if (bytesAvailable() < 8) + return; // cannot read frame headers, wait for more data + + char frameHeadersRaw[8]; + if (!readNextChunk(8, frameHeadersRaw)) + return; // this should not happen, we just checked + + const QByteArray frameHeaders(frameHeadersRaw, 8); // ### try without memcpy + if (frameHeadersRaw[0] & 0x80) { + handleControlFrame(frameHeaders); + } else { + handleDataFrame(frameHeaders); + } + + // after handling the current frame, check whether there is more data waiting + if (m_socket->bytesAvailable() > 0) + QMetaObject::invokeMethod(m_channel, "_q_receiveReply", Qt::QueuedConnection); +} + +void QSpdyProtocolHandler::_q_readyRead() +{ + _q_receiveReply(); +} + +static qint16 twoBytesToInt(const char *bytes) +{ + return qFromBigEndian(reinterpret_cast(bytes)); +} + +static qint32 threeBytesToInt(const char *bytes) +{ + return qFromBigEndian(reinterpret_cast(bytes)) >> 8; +} + +static qint32 fourBytesToInt(const char *bytes) +{ + return qFromBigEndian(reinterpret_cast(bytes)); +} + +static void appendIntToThreeBytes(char *output, qint32 number) +{ + qToBigEndian(number, reinterpret_cast(output + 1)); + qToBigEndian(number >> 16, reinterpret_cast(output)); +} + +static void appendIntToFourBytes(char *output, qint32 number) +{ + qToBigEndian(number, reinterpret_cast(output)); +} + +static QByteArray intToFourBytes(qint32 number) // ### try to use appendIntToFourBytes where possible +{ + uchar data[4]; + qToBigEndian(number, data); + QByteArray ret(reinterpret_cast(data), 4); + return ret; +} + +static QByteArray intToThreeBytes(qint32 number) +{ + uchar data[4]; + qToBigEndian(number << 8, data); + QByteArray ret(reinterpret_cast(data), 3); + return ret; +} + +static qint32 getStreamID(const char *bytes) +{ + // eliminate most significant bit; it might be 0 or 1 depending on whether + // we are dealing with a control or data frame + return fourBytesToInt(bytes) & 0x3fffffff; +} + +static QByteArray headerField(const QByteArray &name, const QByteArray &value) +{ + QByteArray ret; + ret.reserve(name.count() + value.count() + 8); // 4 byte for length each + ret.append(intToFourBytes(name.count())); + ret.append(name); + ret.append(intToFourBytes(value.count())); + ret.append(value); + return ret; +} + +bool QSpdyProtocolHandler::uncompressHeader(const QByteArray &input, QByteArray *output) +{ + const size_t chunkSize = 1024; + char outputRaw[chunkSize]; + // input bytes will not be changed by zlib, so it is safe to const_cast here + m_inflateStream.next_in = const_cast(reinterpret_cast(input.constData())); + m_inflateStream.avail_in = input.count(); + m_inflateStream.total_in = input.count(); + int zlibRet; + + do { + m_inflateStream.next_out = reinterpret_cast(outputRaw); + m_inflateStream.avail_out = chunkSize; + zlibRet = inflate(&m_inflateStream, Z_SYNC_FLUSH); + if (zlibRet == Z_NEED_DICT) { + zlibRet = inflateSetDictionary(&m_inflateStream, + reinterpret_cast(spdyDictionary), + /* dictionaryLength = */ 1423); + Q_ASSERT(zlibRet == Z_OK); + continue; + } + switch (zlibRet) { + case Z_BUF_ERROR: { + if (m_inflateStream.avail_in == 0) { + int outputSize = chunkSize - m_inflateStream.avail_out; + output->append(outputRaw, outputSize); + m_inflateStream.avail_out = chunkSize; + } + break; + } + case Z_OK: { + int outputSize = chunkSize - m_inflateStream.avail_out; + output->append(outputRaw, outputSize); + break; + } + default: { + qWarning() << Q_FUNC_INFO << "got unexpected zlib return value:" << zlibRet; + return false; + } + } + } while (m_inflateStream.avail_in > 0 && zlibRet != Z_STREAM_END); + + Q_ASSERT(m_inflateStream.avail_in == 0); + return true; +} + +QByteArray QSpdyProtocolHandler::composeHeader(const QHttpNetworkRequest &request) +{ + QByteArray uncompressedHeader; + uncompressedHeader.reserve(300); // rough estimate + + // calculate additional headers first, because we need to know the size + // ### do not partially copy the list, but restrict the set header fields + // in QHttpNetworkConnection + QList > additionalHeaders; + for (int a = 0; a < request.header().count(); ++a) { + QByteArray key = request.header().at(a).first; + if (key == "Connection" || key == "Host" || key == "Keep-Alive" + || key == "Proxy-Connection" || key == "Transfer-Encoding") + continue; // those headers are not valid (section 3.2.1) + additionalHeaders.append(request.header().at(a)); + } + + qint32 numberOfHeaderPairs = 5 + additionalHeaders.count(); // 5 mandatory below + the additional ones + uncompressedHeader.append(intToFourBytes(numberOfHeaderPairs)); + + // mandatory header fields: + + uncompressedHeader.append(headerField(":method", request.methodName())); +#ifndef QT_NO_NETWORKPROXY + bool useProxy = m_connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy; + uncompressedHeader.append(headerField(":path", request.uri(useProxy))); +#else + uncompressedHeader.append(headerField(":path", request.uri(false))); +#endif + uncompressedHeader.append(headerField(":version", "HTTP/1.1")); + + QHostAddress add; // ### unify with the host parsing from QHttpNetworkConnection + QByteArray host; + QString hostName = m_connection->hostName(); + if (add.setAddress(hostName)) { + if (add.protocol() == QAbstractSocket::IPv6Protocol) + host = "[" + hostName.toLatin1() + "]"; //format the ipv6 in the standard way + else + host = hostName.toLatin1(); + + } else { + host = QUrl::toAce(hostName); + } + + int port = request.url().port(); + if (port != -1) { + host += ':'; + host += QByteArray::number(port); + } + uncompressedHeader.append(headerField(":host", host)); + + uncompressedHeader.append(headerField(":scheme", request.url().scheme().toLatin1())); + + // end of mandatory header fields + + // now add the additional headers + for (int a = 0; a < additionalHeaders.count(); ++a) { + uncompressedHeader.append(headerField(additionalHeaders.at(a).first.toLower(), + additionalHeaders.at(a).second)); + } + + m_deflateStream.total_in = uncompressedHeader.count(); + m_deflateStream.avail_in = uncompressedHeader.count(); + m_deflateStream.next_in = reinterpret_cast(uncompressedHeader.data()); + int outputBytes = uncompressedHeader.count() + 30; // 30 bytes of compression header overhead + m_deflateStream.avail_out = outputBytes; + unsigned char *out = new unsigned char[outputBytes]; + m_deflateStream.next_out = out; + int availOutBefore = m_deflateStream.avail_out; + int zlibRet = deflate(&m_deflateStream, Z_SYNC_FLUSH); // do everything in one go since we use no compression + int compressedHeaderSize = availOutBefore - m_deflateStream.avail_out; + Q_ASSERT(zlibRet == Z_OK); // otherwise, we need to allocate more outputBytes + Q_UNUSED(zlibRet); // silence -Wunused-variable + Q_ASSERT(m_deflateStream.avail_in == 0); + QByteArray compressedHeader(reinterpret_cast(out), compressedHeaderSize); + delete[] out; + + return compressedHeader; +} + +quint64 QSpdyProtocolHandler::bytesAvailable() const +{ + Q_ASSERT(m_socket); + return m_spdyBuffer.byteAmount() + m_socket->bytesAvailable(); +} + +bool QSpdyProtocolHandler::readNextChunk(qint64 length, char *sink) +{ + qint64 expectedReadBytes = length; + qint64 requiredBytesFromBuffer = 0; + + if (m_waitingForCompleteStream) { + requiredBytesFromBuffer = qMin(length, m_spdyBuffer.byteAmount()); + // ### if next chunk from buffer bigger than what we want to read, + // we have to call read() (which memcpy's). Otherwise, we can just + // read the next chunk without memcpy'ing. + qint64 bytesReadFromBuffer = m_spdyBuffer.read(sink, requiredBytesFromBuffer); + Q_ASSERT(bytesReadFromBuffer == requiredBytesFromBuffer); + if (length <= bytesReadFromBuffer) { + return true; // buffer > required size -> no need to read from socket + } + expectedReadBytes -= requiredBytesFromBuffer; + } + qint64 readBytes = m_socket->read(sink + requiredBytesFromBuffer, expectedReadBytes); + + if (readBytes < expectedReadBytes) { + m_waitingForCompleteStream = true; + // ### this is inefficient, we should not put back so much data into the buffer + QByteArray temp(sink, requiredBytesFromBuffer + readBytes); + m_spdyBuffer.append(temp); + return false; + } else { + return true; // buffer must be cleared by calling function + } +} + +void QSpdyProtocolHandler::sendControlFrame(FrameType type, + ControlFrameFlags flags, + const char *data, + quint32 length) +{ + // frame type and stream ID + char header[8]; + header[0] = 0x80; // leftmost bit == 1 -> is a control frame + header[1] = 0x03; // 3 bit == version 3 + header[2] = 0; + switch (type) { + case FrameType_CREDENTIAL: { + qWarning("sending SPDY CREDENTIAL frame is not yet implemented"); // QTBUG-36188 + return; + } + default: + header[3] = type; + } + + // flags + header[4] = 0; + if (flags & ControlFrame_FLAG_FIN || length == 0) { + Q_ASSERT(type == FrameType_SYN_STREAM || type == FrameType_SYN_REPLY + || type == FrameType_HEADERS || length == 0); + header[4] |= ControlFrame_FLAG_FIN; + } + if (flags & ControlFrame_FLAG_UNIDIRECTIONAL) { + Q_ASSERT(type == FrameType_SYN_STREAM); + header[4] |= ControlFrame_FLAG_UNIDIRECTIONAL; + } + + // length + appendIntToThreeBytes(header + 5, length); + + qint64 written = m_socket->write(header, 8); + Q_ASSERT(written == 8); + written = m_socket->write(data, length); + Q_ASSERT(written == length); +} + +void QSpdyProtocolHandler::sendSYN_STREAM(HttpMessagePair messagePair, + qint32 streamID, qint32 associatedToStreamID) +{ + QHttpNetworkRequest request = messagePair.first; + QHttpNetworkReply *reply = messagePair.second; + + ControlFrameFlags flags = 0; + + if (!request.uploadByteDevice()) { + // no upload -> this is the last frame, send the FIN flag + flags |= ControlFrame_FLAG_FIN; + reply->d_func()->state = QHttpNetworkReplyPrivate::SPDYHalfClosed; + } else { + reply->d_func()->state = QHttpNetworkReplyPrivate::SPDYUploading; + + // hack: set the stream ID on the device directly, so when we get + // the signal for uploading we know which stream we are sending on + request.uploadByteDevice()->setProperty("SPDYStreamID", streamID); + + QObject::connect(request.uploadByteDevice(), SIGNAL(readyRead()), this, + SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection); + } + + QByteArray namesAndValues = composeHeader(request); + quint32 length = namesAndValues.count() + 10; // 10 == 4 for Stream-ID + 4 for Associated-To-Stream-ID + // + 2 for Priority, Unused and Slot + + QByteArray wireData; + wireData.reserve(length); + wireData.append(intToFourBytes(streamID)); + wireData.append(intToFourBytes(associatedToStreamID)); + + // priority (3 bits) / unused (5 bits) / slot (8 bits) + char prioAndSlot[2]; + switch (request.priority()) { + case QHttpNetworkRequest::HighPriority: + prioAndSlot[0] = 0x00; // == prio 0 (highest) + break; + case QHttpNetworkRequest::NormalPriority: + prioAndSlot[0] = 0x80; // == prio 4 + break; + case QHttpNetworkRequest::LowPriority: + prioAndSlot[0] = 0xe0; // == prio 7 (lowest) + break; + } + prioAndSlot[1] = 0x00; // slot in client certificates (not supported currently) + wireData.append(prioAndSlot, 2); + + wireData.append(namesAndValues); + + sendControlFrame(FrameType_SYN_STREAM, flags, wireData.constData(), length); + + if (reply->d_func()->state == QHttpNetworkReplyPrivate::SPDYUploading) + uploadData(streamID); +} + +void QSpdyProtocolHandler::sendRST_STREAM(qint32 streamID, RST_STREAM_STATUS_CODE statusCode) +{ + char wireData[8]; + appendIntToFourBytes(wireData, streamID); + appendIntToFourBytes(wireData + 4, statusCode); + sendControlFrame(FrameType_RST_STREAM, /* flags = */ 0, wireData, /* length = */ 8); +} + +void QSpdyProtocolHandler::sendPING(quint32 pingID) +{ + char rawData[4]; + appendIntToFourBytes(rawData, pingID); + sendControlFrame(FrameType_PING, /* flags = */ 0, rawData, /* length = */ 4); +} + +bool QSpdyProtocolHandler::uploadData(qint32 streamID) +{ + // we only rely on SPDY flow control here and don't care about TCP buffers + + HttpMessagePair messagePair = m_inFlightStreams.value(streamID); + QHttpNetworkRequest request = messagePair.first; + QHttpNetworkReply *reply = messagePair.second; + Q_ASSERT(reply); + QHttpNetworkReplyPrivate *replyPrivate = reply->d_func(); + Q_ASSERT(replyPrivate); + + qint32 dataLeftInWindow = replyPrivate->windowSizeUpload + - replyPrivate->currentlyUploadedDataInWindow; + + while (dataLeftInWindow > 0 && !request.uploadByteDevice()->atEnd()) { + + // get pointer to upload data + qint64 currentReadSize = 0; + const char *readPointer = request.uploadByteDevice()->readPointer(dataLeftInWindow, + currentReadSize); + + if (currentReadSize == -1) { + // premature eof happened + m_connection->d_func()->emitReplyError(m_socket, reply, + QNetworkReply::UnknownNetworkError); + return false; + } else if (readPointer == 0 || currentReadSize == 0) { + // nothing to read currently, break the loop + break; + } else { + DataFrameFlags flags = 0; + // we will send the FIN flag later if appropriate + qint64 currentWriteSize = sendDataFrame(streamID, flags, currentReadSize, readPointer); + if (currentWriteSize == -1 || currentWriteSize != currentReadSize) { + // socket broke down + m_connection->d_func()->emitReplyError(m_socket, reply, + QNetworkReply::UnknownNetworkError); + return false; + } else { + replyPrivate->currentlyUploadedDataInWindow += currentWriteSize; + replyPrivate->totallyUploadedData += currentWriteSize; + dataLeftInWindow = replyPrivate->windowSizeUpload + - replyPrivate->currentlyUploadedDataInWindow; + request.uploadByteDevice()->advanceReadPointer(currentWriteSize); + + emit reply->dataSendProgress(replyPrivate->totallyUploadedData, + request.contentLength()); + } + } + } + if (replyPrivate->totallyUploadedData == request.contentLength()) { + DataFrameFlags finFlag = DataFrame_FLAG_FIN; + qint64 writeSize = sendDataFrame(streamID, finFlag, 0, 0); + Q_ASSERT(writeSize == 0); + Q_UNUSED(writeSize); // silence -Wunused-variable + replyPrivate->state = QHttpNetworkReplyPrivate::SPDYHalfClosed; + // ### this will not work if the content length is not known, but + // then again many servers will fail in this case anyhow according + // to the SPDY RFC + } + return true; +} + +void QSpdyProtocolHandler::_q_uploadDataReadyRead() +{ + QNonContiguousByteDevice *device = qobject_cast(sender()); + Q_ASSERT(device); + qint32 streamID = device->property("SPDYStreamID").toInt(); + Q_ASSERT(streamID > 0); + uploadData(streamID); +} + +void QSpdyProtocolHandler::sendWINDOW_UPDATE(qint32 streamID, quint32 deltaWindowSize) +{ + char windowUpdateData[8]; + appendIntToFourBytes(windowUpdateData, streamID); + appendIntToFourBytes(windowUpdateData + 4, deltaWindowSize); + + sendControlFrame(FrameType_WINDOW_UPDATE, /* flags = */ 0, windowUpdateData, /* length = */ 8); +} + +qint64 QSpdyProtocolHandler::sendDataFrame(qint32 streamID, DataFrameFlags flags, + quint32 length, const char *data) +{ + QByteArray wireData; + wireData.reserve(8); + + wireData.append(intToFourBytes(streamID)); + wireData.append(flags); + wireData.append(intToThreeBytes(length)); + + Q_ASSERT(m_socket); + m_socket->write(wireData); + + if (data) { + qint64 ret = m_socket->write(data, length); + return ret; + } else { + return 0; // nothing to write, e.g. FIN flag + } +} + +void QSpdyProtocolHandler::handleControlFrame(const QByteArray &frameHeaders) // ### make it char * +{ + Q_ASSERT(frameHeaders.count() >= 8); + qint16 version = twoBytesToInt(frameHeaders.constData()); + version &= 0x3fff; // eliminate most significant bit to determine version + Q_ASSERT(version == 3); + + qint16 type = twoBytesToInt(frameHeaders.constData() + 2); + + char flags = frameHeaders.at(4); + qint32 length = threeBytesToInt(frameHeaders.constData() + 5); + Q_ASSERT(length > 0); + + QByteArray frameData; + frameData.resize(length); + if (!readNextChunk(length, frameData.data())) { + // put back the frame headers to the buffer + m_spdyBuffer.prepend(frameHeaders); + return; // we couldn't read the whole frame and need to wait + } else { + m_spdyBuffer.clear(); + m_waitingForCompleteStream = false; + } + + switch (type) { + case FrameType_SYN_STREAM: { + handleSYN_STREAM(flags, length, frameData); + break; + } + case FrameType_SYN_REPLY: { + handleSYN_REPLY(flags, length, frameData); + break; + } + case FrameType_RST_STREAM: { + handleRST_STREAM(flags, length, frameData); + break; + } + case FrameType_SETTINGS: { + handleSETTINGS(flags, length, frameData); + break; + } + case FrameType_PING: { + handlePING(flags, length, frameData); + break; + } + case FrameType_GOAWAY: { + handleGOAWAY(flags, length, frameData); + break; + } + case FrameType_HEADERS: { + handleHEADERS(flags, length, frameData); + break; + } + case FrameType_WINDOW_UPDATE: { + handleWINDOW_UPDATE(flags, length, frameData); + break; + } + default: + qWarning() << Q_FUNC_INFO << "cannot handle frame of type" << type; + } +} + +void QSpdyProtocolHandler::handleSYN_STREAM(char /*flags*/, quint32 /*length*/, + const QByteArray &frameData) +{ + // not implemented; will be implemented when servers start using it + // we just tell the server that we do not accept that + + qint32 streamID = getStreamID(frameData.constData()); + + sendRST_STREAM(streamID, RST_STREAM_REFUSED_STREAM); +} + +void QSpdyProtocolHandler::handleSYN_REPLY(char flags, quint32 /*length*/, const QByteArray &frameData) +{ + parseHttpHeaders(flags, frameData); +} + +void QSpdyProtocolHandler::parseHttpHeaders(char flags, const QByteArray &frameData) +{ + qint32 streamID = getStreamID(frameData.constData()); + + flags &= 0x3f; + bool flag_fin = flags & 0x01; + + QByteArray headerValuePairs = frameData.mid(4); + + HttpMessagePair pair = m_inFlightStreams.value(streamID); + QHttpNetworkReply *httpReply = pair.second; + Q_ASSERT(httpReply != 0); + + QByteArray uncompressedHeader; + if (!uncompressHeader(headerValuePairs, &uncompressedHeader)) { + qWarning() << Q_FUNC_INFO << "error reading header from SYN_REPLY message"; + return; + } + + qint32 headerCount = fourBytesToInt(uncompressedHeader.constData()); + qint32 readPointer = 4; + for (qint32 a = 0; a < headerCount; ++a) { + qint32 count = fourBytesToInt(uncompressedHeader.constData() + readPointer); + readPointer += 4; + QByteArray name = uncompressedHeader.mid(readPointer, count); + readPointer += count; + count = fourBytesToInt(uncompressedHeader.constData() + readPointer); + readPointer += 4; + QByteArray value = uncompressedHeader.mid(readPointer, count); + readPointer += count; + if (name == ":status") { + httpReply->setStatusCode(value.left(3).toInt()); + httpReply->d_func()->reasonPhrase = QString::fromLatin1(value.mid(4)); + } else if (name == ":version") { + int majorVersion = value.at(5) - 48; + int minorVersion = value.at(7) - 48; + httpReply->d_func()->majorVersion = majorVersion; + httpReply->d_func()->minorVersion = minorVersion; + } else if (name == "content-length") { + httpReply->setContentLength(value.toLongLong()); + } else { + httpReply->setHeaderField(name, value); + } + } + emit httpReply->headerChanged(); + + if (flag_fin) { + switch (httpReply->d_func()->state) { + case QHttpNetworkReplyPrivate::SPDYSYNSent: + httpReply->d_func()->state = QHttpNetworkReplyPrivate::SPDYHalfClosed; + break; + case QHttpNetworkReplyPrivate::SPDYHalfClosed: + replyFinished(httpReply, streamID); + break; + case QHttpNetworkReplyPrivate::SPDYClosed: { + sendRST_STREAM(streamID, RST_STREAM_PROTOCOL_ERROR); + replyFinishedWithError(httpReply, streamID, QNetworkReply::ProtocolFailure, + "server sent SYN_REPLY on an already closed stream"); + break; + } + default: + qWarning() << Q_FUNC_INFO << "got data frame in unknown state"; + } + } +} + +void QSpdyProtocolHandler::handleRST_STREAM(char /*flags*/, quint32 length, + const QByteArray &frameData) +{ + // flags are ignored + + Q_ASSERT(length == 8); + Q_UNUSED(length); // silence -Wunused-parameter + qint32 streamID = getStreamID(frameData.constData()); + QHttpNetworkReply *httpReply = m_inFlightStreams.value(streamID).second; + Q_ASSERT(httpReply); + + qint32 statusCodeInt = fourBytesToInt(frameData.constData() + 4); + RST_STREAM_STATUS_CODE statusCode = static_cast(statusCodeInt); + QNetworkReply::NetworkError errorCode; + QByteArray errorMessage; + + switch (statusCode) { + case RST_STREAM_PROTOCOL_ERROR: + errorCode = QNetworkReply::ProtocolFailure; + errorMessage = "SPDY protocol error"; + break; + case RST_STREAM_INVALID_STREAM: + errorCode = QNetworkReply::ProtocolFailure; + errorMessage = "SPDY stream is not active"; + break; + case RST_STREAM_REFUSED_STREAM: + errorCode = QNetworkReply::ProtocolFailure; + errorMessage = "SPDY stream was refused"; + break; + case RST_STREAM_UNSUPPORTED_VERSION: + errorCode = QNetworkReply::ProtocolUnknownError; + errorMessage = "SPDY version is unknown to the server"; + break; + case RST_STREAM_CANCEL: + errorCode = QNetworkReply::ProtocolFailure; + errorMessage = "SPDY stream is no longer needed"; + break; + case RST_STREAM_INTERNAL_ERROR: + errorCode = QNetworkReply::InternalServerError; + errorMessage = "Internal server error"; + break; + case RST_STREAM_FLOW_CONTROL_ERROR: + errorCode = QNetworkReply::ProtocolFailure; + errorMessage = "peer violated the flow control protocol"; + break; + case RST_STREAM_STREAM_IN_USE: + errorCode = QNetworkReply::ProtocolFailure; + errorMessage = "server received a SYN_REPLY for an already open stream"; + break; + case RST_STREAM_STREAM_ALREADY_CLOSED: + errorCode = QNetworkReply::ProtocolFailure; + errorMessage = "server received data or a SYN_REPLY for an already half-closed stream"; + break; + case RST_STREAM_INVALID_CREDENTIALS: + errorCode = QNetworkReply::ContentAccessDenied; + errorMessage = "server received invalid credentials"; + break; + case RST_STREAM_FRAME_TOO_LARGE: + errorCode = QNetworkReply::ProtocolFailure; + errorMessage = "server cannot process the frame because it is too large"; + break; + default: + qWarning() << Q_FUNC_INFO << "could not understand servers RST_STREAM status code"; + errorCode = QNetworkReply::ProtocolFailure; + errorMessage = "got SPDY RST_STREAM message with unknown error code"; + } + replyFinishedWithError(httpReply, streamID, errorCode, errorMessage.constData()); +} + +void QSpdyProtocolHandler::handleSETTINGS(char flags, quint32 /*length*/, const QByteArray &frameData) +{ + Q_ASSERT(frameData.count() > 0); + + SETTINGS_Flags settingsFlags = static_cast(flags); + if (settingsFlags & FLAG_SETTINGS_CLEAR_SETTINGS) { + // ### clear all persistent settings; since we do not persist settings + // as of now, we don't need to clear anything either + } + + qint32 numberOfEntries = fourBytesToInt(frameData.constData()); + Q_ASSERT(numberOfEntries > 0); + for (int a = 0, frameDataIndex = 4; a < numberOfEntries; ++a, frameDataIndex += 8) { + SETTINGS_ID_Flag idFlag = static_cast(frameData[frameDataIndex]); + if (idFlag & FLAG_SETTINGS_PERSIST_VALUE) { + // ### we SHOULD persist the settings here according to the RFC, but we don't have to, + // so implement that later + } // the other value is only sent by us, but not received + + quint32 uniqueID = static_cast( + threeBytesToInt(frameData.constData() + frameDataIndex + 1)); + quint32 value = fourBytesToInt(frameData.constData() + frameDataIndex + 4); + switch (uniqueID) { + case SETTINGS_UPLOAD_BANDWIDTH: { + // ignored for now, just an estimated informative value + break; + } + case SETTINGS_DOWNLOAD_BANDWIDTH: { + // ignored for now, just an estimated informative value + break; + } + case SETTINGS_ROUND_TRIP_TIME: { + // ignored for now, just an estimated informative value + break; + } + case SETTINGS_MAX_CONCURRENT_STREAMS: { + m_maxConcurrentStreams = value; + break; + } + case SETTINGS_CURRENT_CWND: { + // ignored for now, just an informative value + break; + } + case SETTINGS_DOWNLOAD_RETRANS_RATE: { + // ignored for now, just an estimated informative value + break; + } + case SETTINGS_INITIAL_WINDOW_SIZE: { + m_initialWindowSize = value; + break; + } + case SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE: { + // client certificates are not supported + break; + } + default: + qWarning() << Q_FUNC_INFO << "found unknown settings value" << value; + } + } +} + +void QSpdyProtocolHandler::handlePING(char /*flags*/, quint32 length, const QByteArray &frameData) +{ + // flags are ignored + + Q_ASSERT(length == 4); + Q_UNUSED(length); // silence -Wunused-parameter + quint32 pingID = fourBytesToInt(frameData.constData()); + + // odd numbered IDs must be ignored + if ((pingID & 1) == 0) // is even? + sendPING(pingID); +} + +void QSpdyProtocolHandler::handleGOAWAY(char /*flags*/, quint32 /*length*/, + const QByteArray &frameData) +{ + // flags are ignored + + qint32 statusCode = static_cast(fourBytesToInt(frameData.constData() + 4)); + QNetworkReply::NetworkError errorCode; + switch (statusCode) { + case GOAWAY_OK: { + errorCode = QNetworkReply::NoError; + break; + } + case GOAWAY_PROTOCOL_ERROR: { + errorCode = QNetworkReply::ProtocolFailure; + break; + } + case GOAWAY_INTERNAL_ERROR: { + errorCode = QNetworkReply::InternalServerError; + break; + } + default: + qWarning() << Q_FUNC_INFO << "unexpected status code" << statusCode; + errorCode = QNetworkReply::ProtocolUnknownError; + } + + qint32 lastGoodStreamID = getStreamID(frameData.constData()); + + // emit errors for all replies after the last good stream ID + Q_ASSERT(m_connection); + for (qint32 currentStreamID = lastGoodStreamID + 2; currentStreamID <= m_nextStreamID; + ++currentStreamID) { + QHttpNetworkReply *reply = m_inFlightStreams.value(currentStreamID).second; + Q_ASSERT(reply); + m_connection->d_func()->emitReplyError(m_socket, reply, errorCode); + } + // ### we could make sure a new session is initiated anyhow +} + +void QSpdyProtocolHandler::handleHEADERS(char flags, quint32 /*length*/, + const QByteArray &frameData) +{ + parseHttpHeaders(flags, frameData); +} + +void QSpdyProtocolHandler::handleWINDOW_UPDATE(char /*flags*/, quint32 /*length*/, + const QByteArray &frameData) +{ + qint32 streamID = getStreamID(frameData.constData()); + qint32 deltaWindowSize = fourBytesToInt(frameData.constData() + 4); + + QHttpNetworkReply *reply = m_inFlightStreams.value(streamID).second; + Q_ASSERT(reply); + QHttpNetworkReplyPrivate *replyPrivate = reply->d_func(); + Q_ASSERT(replyPrivate); + + replyPrivate->currentlyUploadedDataInWindow = replyPrivate->windowSizeUpload - deltaWindowSize; + uploadData(streamID); // we hopefully can continue to upload +} + + +void QSpdyProtocolHandler::handleDataFrame(const QByteArray &frameHeaders) +{ + Q_ASSERT(frameHeaders.count() >= 8); + + qint32 streamID = getStreamID(frameHeaders.constData()); + unsigned char flags = static_cast(frameHeaders.at(4)); + flags &= 0x3f; + bool flag_fin = flags & 0x01; + bool flag_compress = flags & 0x02; + qint32 length = threeBytesToInt(frameHeaders.constData() + 5); + + QByteArray data; + data.resize(length); + if (!readNextChunk(length, data.data())) { + // put back the frame headers to the buffer + m_spdyBuffer.prepend(frameHeaders); + return; // we couldn't read the whole frame and need to wait + } else { + m_spdyBuffer.clear(); + m_waitingForCompleteStream = false; + } + + HttpMessagePair pair = m_inFlightStreams.value(streamID); + QHttpNetworkRequest httpRequest = pair.first; + QHttpNetworkReply *httpReply = pair.second; + Q_ASSERT(httpReply != 0); + + QHttpNetworkReplyPrivate *replyPrivate = httpReply->d_func(); + + // check whether we need to send WINDOW_UPDATE (i.e. tell the sender it can send more) + replyPrivate->currentlyReceivedDataInWindow += length; + qint32 dataLeftInWindow = replyPrivate->windowSizeDownload - replyPrivate->currentlyReceivedDataInWindow; + + if (replyPrivate->currentlyReceivedDataInWindow > 0 + && dataLeftInWindow < replyPrivate->windowSizeDownload / 2) { + + // socket read buffer size is 64K actually, hard coded in the channel + // We can read way more than 64K per socket, because the window size + // here is per stream. + if (replyPrivate->windowSizeDownload >= m_socket->readBufferSize()) { + replyPrivate->windowSizeDownload = m_socket->readBufferSize(); + } else { + replyPrivate->windowSizeDownload *= 1.5; + } + QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection, + Q_ARG(qint32, streamID), + Q_ARG(quint32, replyPrivate->windowSizeDownload)); + // setting the current data count to 0 is a race condition, + // because we call sendWINDOW_UPDATE through the event loop. + // But then again, the whole situation is a race condition because + // we don't know when the packet will arrive at the server; so + // this is most likely good enough here. + replyPrivate->currentlyReceivedDataInWindow = 0; + } + + httpReply->d_func()->compressedData.append(data); + + + replyPrivate->totalProgress += length; + + if (httpRequest.d->autoDecompress && httpReply->d_func()->isCompressed()) { + QByteDataBuffer inDataBuffer; // ### should we introduce one in the http reply? + inDataBuffer.append(data); + qint64 compressedCount = httpReply->d_func()->uncompressBodyData(&inDataBuffer, + &replyPrivate->responseData); + Q_ASSERT(compressedCount >= 0); + Q_UNUSED(compressedCount); // silence -Wunused-variable + } else { + replyPrivate->responseData.append(data); + } + + if (replyPrivate->shouldEmitSignals()) { + emit httpReply->readyRead(); + emit httpReply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); + } + + if (flag_compress) { + qWarning() << Q_FUNC_INFO << "SPDY level compression is not supported"; + } + + if (flag_fin) { + switch (httpReply->d_func()->state) { + case QHttpNetworkReplyPrivate::SPDYSYNSent: + httpReply->d_func()->state = QHttpNetworkReplyPrivate::SPDYHalfClosed; + // ### send FIN ourselves? + break; + case QHttpNetworkReplyPrivate::SPDYHalfClosed: + replyFinished(httpReply, streamID); + break; + case QHttpNetworkReplyPrivate::SPDYClosed: { + sendRST_STREAM(streamID, RST_STREAM_PROTOCOL_ERROR); + replyFinishedWithError(httpReply, streamID, QNetworkReply::ProtocolFailure, + "server sent data on an already closed stream"); + break; + } + default: + qWarning() << Q_FUNC_INFO << "got data frame in unknown state"; + } + } +} + +void QSpdyProtocolHandler::replyFinished(QHttpNetworkReply *httpReply, qint32 streamID) +{ + httpReply->d_func()->state = QHttpNetworkReplyPrivate::SPDYClosed; + int streamsRemoved = m_inFlightStreams.remove(streamID); + Q_ASSERT(streamsRemoved == 1); + Q_UNUSED(streamsRemoved); // silence -Wunused-variable + emit httpReply->finished(); +} + +void QSpdyProtocolHandler::replyFinishedWithError(QHttpNetworkReply *httpReply, qint32 streamID, + QNetworkReply::NetworkError errorCode, const char *errorMessage) +{ + httpReply->d_func()->state = QHttpNetworkReplyPrivate::SPDYClosed; + int streamsRemoved = m_inFlightStreams.remove(streamID); + Q_ASSERT(streamsRemoved == 1); + Q_UNUSED(streamsRemoved); // silence -Wunused-variable + emit httpReply->finishedWithError(errorCode, QSpdyProtocolHandler::tr(errorMessage)); +} + +qint32 QSpdyProtocolHandler::generateNextStreamID() +{ + // stream IDs initiated by the client must be odd + m_nextStreamID += 2; + return m_nextStreamID; +} + +QT_END_NAMESPACE + +#endif // !defined(QT_NO_HTTP) && !defined(QT_NO_SSL) diff --git a/src/network/access/qspdyprotocolhandler_p.h b/src/network/access/qspdyprotocolhandler_p.h new file mode 100644 index 0000000000..8cbfbdda86 --- /dev/null +++ b/src/network/access/qspdyprotocolhandler_p.h @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSPDYPROTOCOLHANDLER_H +#define QSPDYPROTOCOLHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Network Access API. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +#include + +#if !defined(QT_NO_HTTP) && !defined(QT_NO_SSL) + +QT_BEGIN_NAMESPACE + +class QHttpNetworkRequest; + +#ifndef HttpMessagePair +typedef QPair HttpMessagePair; +#endif + +class QSpdyProtocolHandler : public QObject, public QAbstractProtocolHandler { + Q_OBJECT +public: + QSpdyProtocolHandler(QHttpNetworkConnectionChannel *channel); + ~QSpdyProtocolHandler(); + + enum DataFrameFlag { + DataFrame_FLAG_FIN = 0x01, + DataFrame_FLAG_COMPRESS = 0x02 + }; + + Q_DECLARE_FLAGS(DataFrameFlags, DataFrameFlag) + + enum ControlFrameFlag { + ControlFrame_FLAG_FIN = 0x01, + ControlFrame_FLAG_UNIDIRECTIONAL = 0x02 + }; + + Q_DECLARE_FLAGS(ControlFrameFlags, ControlFrameFlag) + + enum SETTINGS_Flag { + FLAG_SETTINGS_CLEAR_SETTINGS = 0x01 + }; + + Q_DECLARE_FLAGS(SETTINGS_Flags, SETTINGS_Flag) + + enum SETTINGS_ID_Flag { + FLAG_SETTINGS_PERSIST_VALUE = 0x01, + FLAG_SETTINGS_PERSISTED = 0x02 + }; + + Q_DECLARE_FLAGS(SETTINGS_ID_Flags, SETTINGS_ID_Flag) + + virtual void _q_receiveReply() Q_DECL_OVERRIDE; + virtual void _q_readyRead() Q_DECL_OVERRIDE; + virtual bool sendRequest() Q_DECL_OVERRIDE; + +private slots: + void _q_uploadDataReadyRead(); + +private: + + enum FrameType { + FrameType_SYN_STREAM = 1, + FrameType_SYN_REPLY = 2, + FrameType_RST_STREAM = 3, + FrameType_SETTINGS = 4, + FrameType_PING = 6, + FrameType_GOAWAY = 7, + FrameType_HEADERS = 8, + FrameType_WINDOW_UPDATE = 9, + FrameType_CREDENTIAL // has a special type + }; + + enum StatusCode { + StatusCode_PROTOCOL_ERROR = 1, + StatusCode_INVALID_STREAM = 2, + StatusCode_REFUSED_STREAM = 3, + StatusCode_UNSUPPORTED_VERSION = 4, + StatusCode_CANCEL = 5, + StatusCode_INTERNAL_ERROR = 6, + StatusCode_FLOW_CONTROL_ERROR = 7, + StatusCode_STREAM_IN_USE = 8, + StatusCode_STREAM_ALREADY_CLOSED = 9, + StatusCode_INVALID_CREDENTIALS = 10, + StatusCode_FRAME_TOO_LARGE = 11 + }; + + enum SETTINGS_ID { + SETTINGS_UPLOAD_BANDWIDTH = 1, + SETTINGS_DOWNLOAD_BANDWIDTH = 2, + SETTINGS_ROUND_TRIP_TIME = 3, + SETTINGS_MAX_CONCURRENT_STREAMS = 4, + SETTINGS_CURRENT_CWND = 5, + SETTINGS_DOWNLOAD_RETRANS_RATE = 6, + SETTINGS_INITIAL_WINDOW_SIZE = 7, + SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8 + }; + + enum GOAWAY_STATUS { + GOAWAY_OK = 0, + GOAWAY_PROTOCOL_ERROR = 1, + GOAWAY_INTERNAL_ERROR = 11 + }; + + enum RST_STREAM_STATUS_CODE { + RST_STREAM_PROTOCOL_ERROR = 1, + RST_STREAM_INVALID_STREAM = 2, + RST_STREAM_REFUSED_STREAM = 3, + RST_STREAM_UNSUPPORTED_VERSION = 4, + RST_STREAM_CANCEL = 5, + RST_STREAM_INTERNAL_ERROR = 6, + RST_STREAM_FLOW_CONTROL_ERROR = 7, + RST_STREAM_STREAM_IN_USE = 8, + RST_STREAM_STREAM_ALREADY_CLOSED = 9, + RST_STREAM_INVALID_CREDENTIALS = 10, + RST_STREAM_FRAME_TOO_LARGE = 11 + }; + + quint64 bytesAvailable() const; + bool readNextChunk(qint64 length, char *sink); + + void sendControlFrame(FrameType type, ControlFrameFlags flags, const char *data, quint32 length); + + void sendSYN_STREAM(HttpMessagePair pair, qint32 streamID, + qint32 associatedToStreamID); + void sendRST_STREAM(qint32 streamID, RST_STREAM_STATUS_CODE statusCode); + void sendPING(quint32 pingID); + + bool uploadData(qint32 streamID); + Q_INVOKABLE void sendWINDOW_UPDATE(qint32 streamID, quint32 deltaWindowSize); + + qint64 sendDataFrame(qint32 streamID, DataFrameFlags flags, quint32 length, + const char *data); + + QByteArray composeHeader(const QHttpNetworkRequest &request); + bool uncompressHeader(const QByteArray &input, QByteArray *output); + + void handleControlFrame(const QByteArray &frameHeaders); + void handleDataFrame(const QByteArray &frameHeaders); + + void handleSYN_STREAM(char, quint32, const QByteArray &frameData); + void handleSYN_REPLY(char flags, quint32, const QByteArray &frameData); + void handleRST_STREAM(char flags, quint32 length, const QByteArray &frameData); + void handleSETTINGS(char flags, quint32 length, const QByteArray &frameData); + void handlePING(char, quint32 length, const QByteArray &frameData); + void handleGOAWAY(char flags, quint32, const QByteArray &frameData); + void handleHEADERS(char flags, quint32, const QByteArray &frameData); + void handleWINDOW_UPDATE(char, quint32, const QByteArray &frameData); + + qint32 generateNextStreamID(); + void parseHttpHeaders(char flags, const QByteArray &frameData); + + void replyFinished(QHttpNetworkReply *httpReply, qint32 streamID); + void replyFinishedWithError(QHttpNetworkReply *httpReply, qint32 streamID, + QNetworkReply::NetworkError errorCode, const char *errorMessage); + + qint32 m_nextStreamID; + QHash m_inFlightStreams; + qint32 m_maxConcurrentStreams; + quint32 m_initialWindowSize; + QByteDataBuffer m_spdyBuffer; + bool m_waitingForCompleteStream; + z_stream m_deflateStream; + z_stream m_inflateStream; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QSpdyProtocolHandler::DataFrameFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QSpdyProtocolHandler::ControlFrameFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QSpdyProtocolHandler::SETTINGS_Flags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QSpdyProtocolHandler::SETTINGS_ID_Flags) + +QT_END_NAMESPACE + +#endif // !defined(QT_NO_HTTP) && !defined(QT_NO_SSL) + +#endif // QSPDYPROTOCOLHANDLER_H -- cgit v1.2.3