diff options
author | Simon Hausmann <simon.hausmann@digia.com> | 2014-02-28 13:13:16 +0100 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@digia.com> | 2014-02-28 13:14:15 +0100 |
commit | 846cc4c75cc06def67e831c8cb3c9edaef24f8f0 (patch) | |
tree | cf8cd76a5a610a6264583148b2c0549f7514dde3 /src/network/access | |
parent | 9e4a215a2569a04010292c8718d0f0569bc06c12 (diff) | |
parent | 0d0fb04d505d105fb4b2fc71d68f729ce670b12e (diff) |
Merge remote-tracking branch 'origin/stable' into dev
Conflicts:
src/network/access/qhttpthreaddelegate.cpp
Change-Id: Ia15372687c93cd585967b006c0baaac3a5f29e91
Diffstat (limited to 'src/network/access')
-rw-r--r-- | src/network/access/access.pri | 2 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnection.cpp | 154 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnection_p.h | 37 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel.cpp | 130 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel_p.h | 8 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkreply.cpp | 50 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkreply_p.h | 16 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkrequest.cpp | 16 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkrequest_p.h | 5 | ||||
-rw-r--r-- | src/network/access/qhttpthreaddelegate.cpp | 38 | ||||
-rw-r--r-- | src/network/access/qhttpthreaddelegate_p.h | 4 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.cpp | 13 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyhttpimpl.cpp | 20 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyhttpimpl_p.h | 7 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.cpp | 11 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.h | 2 | ||||
-rw-r--r-- | src/network/access/qspdyprotocolhandler.cpp | 1286 | ||||
-rw-r--r-- | src/network/access/qspdyprotocolhandler_p.h | 228 |
18 files changed, 1945 insertions, 82 deletions
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 866a9399a9..2421de3f0c 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<HttpMessagePair> 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<QNetworkSession> networkSession) - : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent) +QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, + QHttpNetworkConnection::ConnectionType connectionType, + QObject *parent, QSharedPointer<QNetworkSession> networkSession) + : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt, connectionType)), parent) { Q_D(QHttpNetworkConnection); d->networkSession = qMove(networkSession); d->init(); } -QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent, QSharedPointer<QNetworkSession> networkSession) - : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt)), parent) +QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, + quint16 port, bool encrypt, QObject *parent, + QSharedPointer<QNetworkSession> networkSession, + QHttpNetworkConnection::ConnectionType connectionType) + : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt, + connectionType)), parent) { Q_D(QHttpNetworkConnection); d->networkSession = qMove(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<QNetworkSession> networkSession = QSharedPointer<QNetworkSession>()); - QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0, QSharedPointer<QNetworkSession> networkSession = QSharedPointer<QNetworkSession>()); + explicit QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, + ConnectionType connectionType = ConnectionTypeHTTP, + QObject *parent = 0, QSharedPointer<QNetworkSession> networkSession + = QSharedPointer<QNetworkSession>()); + QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, + bool encrypt = false, QObject *parent = 0, + QSharedPointer<QNetworkSession> networkSession = QSharedPointer<QNetworkSession>(), + 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<QSslContext> 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 <private/qhttpprotocolhandler_p.h> +#include <private/qspdyprotocolhandler_p.h> #ifndef QT_NO_SSL # include <QtNetwork/qsslkey.h> @@ -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<HttpMessagePair> 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<QSslSocket *>(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<HttpMessagePair> 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<HttpMessagePair> 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<QSslError> &errors) @@ -927,8 +1020,21 @@ void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &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<HttpMessagePair> 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<QSslError> ignoreSslErrorsList; QSslConfiguration sslConfiguration; + QMultiMap<int, HttpMessagePair> spdyRequestsToSend; // sorted by priority void ignoreSslErrors(); void ignoreSslErrors(const QList<QSslError> &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<QNetworkSession> 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<QHttpNetworkConnection> connection; QPointer<QHttpNetworkConnectionChannel> 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 5602c8028a..7b5ad1e2f1 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<QNetworkSession> networkSession) - : QHttpNetworkConnection(hostName, port, encrypt, /*parent=*/0, qMove(networkSession)) + QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt, + QHttpNetworkConnection::ConnectionType connectionType, + QSharedPointer<QNetworkSession> networkSession) + : QHttpNetworkConnection(hostName, port, encrypt, connectionType, /*parent=*/0, + qMove(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<QByteArray> 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<QSslError> &, bool *, QList<QSslError> *); void sslConfigurationChanged(const QSslConfiguration); #endif - void downloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64); + void downloadMetaData(QList<QPair<QByteArray,QByteArray> >, int, QString, bool, + QSharedPointer<char>, 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<QNetworkRequest::LoadControl> (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<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)), - q, SLOT(replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)), + QObject::connect(delegate, SIGNAL(downloadMetaData(QList<QPair<QByteArray,QByteArray> >, + int, QString, bool, + QSharedPointer<char>, qint64, bool)), + q, SLOT(replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >, + int, QString, bool, + QSharedPointer<char>, 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<char>(), - 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<char>(), - delegate->incomingContentLength); + delegate->incomingContentLength, + delegate->isSpdyUsed); replyDownloadData(delegate->synchronousDownloadData); } @@ -1074,7 +1083,7 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData (QList<QPair<QByteArray,QByteArray> > hm, int sc,QString rp,bool pu, QSharedPointer<char> 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<QPair<QByteArray, QByteArray> > 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<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)) + Q_PRIVATE_SLOT(d_func(), void replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >, + int, QString, bool, QSharedPointer<char>, + 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<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64); + void replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >, int, QString, bool, + QSharedPointer<char>, 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..098b3e9ab0 --- /dev/null +++ b/src/network/access/qspdyprotocolhandler.cpp @@ -0,0 +1,1286 @@ +/**************************************************************************** +** +** 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 <private/qspdyprotocolhandler_p.h> +#include <private/qnoncontiguousbytedevice_p.h> +#include <private/qhttpnetworkconnectionchannel_p.h> +#include <QtCore/QtEndian> + +#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<HttpMessagePair> requests = m_channel->spdyRequestsToSend.values(); + QList<int> 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<QHttpNetworkConnection*>(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<qint16>(reinterpret_cast<const uchar *>(bytes)); +} + +static qint32 threeBytesToInt(const char *bytes) +{ + return qFromBigEndian<qint32>(reinterpret_cast<const uchar *>(bytes)) >> 8; +} + +static qint32 fourBytesToInt(const char *bytes) +{ + return qFromBigEndian<qint32>(reinterpret_cast<const uchar *>(bytes)); +} + +static void appendIntToThreeBytes(char *output, qint32 number) +{ + qToBigEndian<qint16>(number, reinterpret_cast<uchar *>(output + 1)); + qToBigEndian<qint8>(number >> 16, reinterpret_cast<uchar *>(output)); +} + +static void appendIntToFourBytes(char *output, qint32 number) +{ + qToBigEndian<qint32>(number, reinterpret_cast<uchar *>(output)); +} + +static QByteArray intToFourBytes(qint32 number) // ### try to use appendIntToFourBytes where possible +{ + uchar data[4]; + qToBigEndian<qint32>(number, data); + QByteArray ret(reinterpret_cast<char *>(data), 4); + return ret; +} + +static QByteArray intToThreeBytes(qint32 number) +{ + uchar data[4]; + qToBigEndian<qint32>(number << 8, data); + QByteArray ret(reinterpret_cast<char *>(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<Bytef *>(reinterpret_cast<const Bytef *>(input.constData())); + m_inflateStream.avail_in = input.count(); + m_inflateStream.total_in = input.count(); + int zlibRet; + + do { + m_inflateStream.next_out = reinterpret_cast<Bytef *>(outputRaw); + m_inflateStream.avail_out = chunkSize; + zlibRet = inflate(&m_inflateStream, Z_SYNC_FLUSH); + if (zlibRet == Z_NEED_DICT) { + zlibRet = inflateSetDictionary(&m_inflateStream, + reinterpret_cast<const Bytef*>(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<QPair<QByteArray, QByteArray> > 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<unsigned char *>(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<char *>(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<QNonContiguousByteDevice *>(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 { + if (value.contains('\0')) { + QList<QByteArray> values = value.split('\0'); + QByteArray binder(", "); + if (name == "set-cookie") + binder = "\n"; + value.clear(); + Q_FOREACH (const QByteArray& ivalue, values) { + if (value.isEmpty()) + value = ivalue; + else + value += binder + ivalue; + } + } + 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; + + qint32 statusCodeInt = fourBytesToInt(frameData.constData() + 4); + RST_STREAM_STATUS_CODE statusCode = static_cast<RST_STREAM_STATUS_CODE>(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"; + } + if (httpReply) + 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<SETTINGS_Flags>(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<SETTINGS_ID_Flag>(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<SETTINGS_ID>( + 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<GOAWAY_STATUS>(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<unsigned char>(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) +{ + Q_ASSERT(httpReply); + 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 <private/qabstractprotocolhandler_p.h> +#include <QtNetwork/qnetworkreply.h> +#include <private/qbytedata_p.h> + +#include <zlib.h> + +#if !defined(QT_NO_HTTP) && !defined(QT_NO_SSL) + +QT_BEGIN_NAMESPACE + +class QHttpNetworkRequest; + +#ifndef HttpMessagePair +typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> 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<quint32, HttpMessagePair> 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 |