summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@theqtcompany.com>2016-01-12 14:45:47 +0100
committerTimur Pocheptsov <timur.pocheptsov@theqtcompany.com>2016-07-08 16:22:32 +0000
commitb2c0f9713c1c39bc3214df49f0d2b44bcc84d790 (patch)
tree6a8eee65f90dd67968fd239606354d49f2202342 /src/network
parent6f61cfc33810054173a17443b20bfa2dce737285 (diff)
HTTP2 protocol handler
Add HTTP2 support in QNAM - QHttp2ProtocolHandler + aux. classes. [ChangeLog][QtNetwork] Added support for HTTP/2 protocol Task-number: QTBUG-50956 Change-Id: I3a119cfbb1fb3118d9003225f5e54b46ae2829b6 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'src/network')
-rw-r--r--src/network/access/access.pri6
-rw-r--r--src/network/access/http2/http2.pri10
-rw-r--r--src/network/access/http2/http2frames.cpp525
-rw-r--r--src/network/access/http2/http2frames_p.h173
-rw-r--r--src/network/access/http2/http2protocol.cpp156
-rw-r--r--src/network/access/http2/http2protocol_p.h164
-rw-r--r--src/network/access/http2/http2streams.cpp100
-rw-r--r--src/network/access/http2/http2streams_p.h92
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp1219
-rw-r--r--src/network/access/qhttp2protocolhandler_p.h196
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp6
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h4
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp14
-rw-r--r--src/network/access/qhttpnetworkreply_p.h1
-rw-r--r--src/network/access/qhttpnetworkrequest.cpp14
-rw-r--r--src/network/access/qhttpnetworkrequest_p.h5
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp8
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp3
-rw-r--r--src/network/access/qnetworkrequest.h2
-rw-r--r--src/network/ssl/qsslconfiguration.cpp1
-rw-r--r--src/network/ssl/qsslconfiguration.h1
21 files changed, 2688 insertions, 12 deletions
diff --git a/src/network/access/access.pri b/src/network/access/access.pri
index 746ddc6916..70ace3f55e 100644
--- a/src/network/access/access.pri
+++ b/src/network/access/access.pri
@@ -38,7 +38,8 @@ HEADERS += \
access/qhttpthreaddelegate_p.h \
access/qhttpmultipart.h \
access/qhttpmultipart_p.h \
- access/qnetworkfile_p.h
+ access/qnetworkfile_p.h \
+ access/qhttp2protocolhandler_p.h
SOURCES += \
access/qftp.cpp \
@@ -70,7 +71,8 @@ SOURCES += \
access/qnetworkdiskcache.cpp \
access/qhttpthreaddelegate.cpp \
access/qhttpmultipart.cpp \
- access/qnetworkfile.cpp
+ access/qnetworkfile.cpp \
+ access/qhttp2protocolhandler.cpp
mac: LIBS_PRIVATE += -framework Security
diff --git a/src/network/access/http2/http2.pri b/src/network/access/http2/http2.pri
index 2157e35e2f..e9f30aeb4a 100644
--- a/src/network/access/http2/http2.pri
+++ b/src/network/access/http2/http2.pri
@@ -2,10 +2,16 @@ HEADERS += \
access/http2/bitstreams_p.h \
access/http2/huffman_p.h \
access/http2/hpack_p.h \
- access/http2/hpacktable_p.h
+ access/http2/hpacktable_p.h \
+ access/http2/http2frames_p.h \
+ access/http2/http2streams_p.h \
+ access/http2/http2protocol_p.h
SOURCES += \
access/http2/bitstreams.cpp \
access/http2/huffman.cpp \
access/http2/hpack.cpp \
- access/http2/hpacktable.cpp
+ access/http2/hpacktable.cpp \
+ access/http2/http2frames.cpp \
+ access/http2/http2streams.cpp \
+ access/http2/http2protocol.cpp
diff --git a/src/network/access/http2/http2frames.cpp b/src/network/access/http2/http2frames.cpp
new file mode 100644
index 0000000000..471fb2c7fb
--- /dev/null
+++ b/src/network/access/http2/http2frames.cpp
@@ -0,0 +1,525 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "http2frames_p.h"
+
+#include <QtNetwork/qabstractsocket.h>
+
+#include <algorithm>
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+namespace Http2
+{
+
+// HTTP/2 frames are defined by RFC7540, clauses 4 and 6.
+
+FrameStatus validate_frame_header(FrameType type, FrameFlags flags, quint32 payloadSize)
+{
+ // 4.2 Frame Size
+ if (payloadSize > maxPayloadSize)
+ return FrameStatus::sizeError;
+
+ switch (type) {
+ case FrameType::SETTINGS:
+ // SETTINGS ACK can not have any payload.
+ // The payload of a SETTINGS frame consists of zero
+ // or more parameters, each consisting of an unsigned
+ // 16-bit setting identifier and an unsigned 32-bit value.
+ // Thus the payload size must be a multiple of 6.
+ if (flags.testFlag(FrameFlag::ACK) ? payloadSize : payloadSize % 6)
+ return FrameStatus::sizeError;
+ break;
+ case FrameType::PRIORITY:
+ // 6.3 PRIORITY
+ if (payloadSize != 5)
+ return FrameStatus::sizeError;
+ break;
+ case FrameType::PING:
+ case FrameType::GOAWAY:
+ // 6.7 PING, 6.8 GOAWAY
+ if (payloadSize != 8)
+ return FrameStatus::sizeError;
+ break;
+ case FrameType::RST_STREAM:
+ case FrameType::WINDOW_UPDATE:
+ // 6.4 RST_STREAM, 6.9 WINDOW_UPDATE
+ if (payloadSize != 4)
+ return FrameStatus::sizeError;
+ break;
+ case FrameType::PUSH_PROMISE:
+ // 6.6 PUSH_PROMISE
+ if (payloadSize < 4)
+ return FrameStatus::sizeError;
+ default:
+ // DATA/HEADERS/CONTINUATION will be verified
+ // when we have payload.
+ // Frames of unknown types are ignored (5.1)
+ break;
+ }
+
+ return FrameStatus::goodFrame;
+}
+
+FrameStatus validate_frame_payload(FrameType type, FrameFlags flags,
+ quint32 size, const uchar *src)
+{
+ Q_ASSERT(!size || src);
+
+ // Ignored, 5.1
+ if (type == FrameType::LAST_FRAME_TYPE)
+ return FrameStatus::goodFrame;
+
+ // 6.1 DATA, 6.2 HEADERS
+ if (type == FrameType::DATA || type == FrameType::HEADERS) {
+ if (flags.testFlag(FrameFlag::PADDED)) {
+ if (!size || size < src[0])
+ return FrameStatus::sizeError;
+ size -= src[0];
+ }
+ if (type == FrameType::HEADERS && flags.testFlag(FrameFlag::PRIORITY)) {
+ if (size < 5)
+ return FrameStatus::sizeError;
+ }
+ }
+
+ // 6.6 PUSH_PROMISE
+ if (type == FrameType::PUSH_PROMISE) {
+ if (flags.testFlag(FrameFlag::PADDED)) {
+ if (!size || size < src[0])
+ return FrameStatus::sizeError;
+ size -= src[0];
+ }
+
+ if (size < 4)
+ return FrameStatus::sizeError;
+ }
+
+ return FrameStatus::goodFrame;
+}
+
+FrameStatus validate_frame_payload(FrameType type, FrameFlags flags,
+ const std::vector<uchar> &payload)
+{
+ const uchar *src = payload.size() ? &payload[0] : nullptr;
+ return validate_frame_payload(type, flags, quint32(payload.size()), src);
+}
+
+
+FrameReader::FrameReader(FrameReader &&rhs)
+ : framePayload(std::move(rhs.framePayload))
+{
+ type = rhs.type;
+ rhs.type = FrameType::LAST_FRAME_TYPE;
+
+ flags = rhs.flags;
+ rhs.flags = FrameFlag::EMPTY;
+
+ streamID = rhs.streamID;
+ rhs.streamID = 0;
+
+ payloadSize = rhs.payloadSize;
+ rhs.payloadSize = 0;
+
+ incompleteRead = rhs.incompleteRead;
+ rhs.incompleteRead = false;
+
+ offset = rhs.offset;
+ rhs.offset = 0;
+}
+
+FrameReader &FrameReader::operator = (FrameReader &&rhs)
+{
+ framePayload = std::move(rhs.framePayload);
+
+ type = rhs.type;
+ rhs.type = FrameType::LAST_FRAME_TYPE;
+
+ flags = rhs.flags;
+ rhs.flags = FrameFlag::EMPTY;
+
+ streamID = rhs.streamID;
+ rhs.streamID = 0;
+
+ payloadSize = rhs.payloadSize;
+ rhs.payloadSize = 0;
+
+ incompleteRead = rhs.incompleteRead;
+ rhs.incompleteRead = false;
+
+ offset = rhs.offset;
+ rhs.offset = 0;
+
+ return *this;
+}
+
+FrameStatus FrameReader::read(QAbstractSocket &socket)
+{
+ if (!incompleteRead) {
+ if (!readHeader(socket))
+ return FrameStatus::incompleteFrame;
+
+ const auto status = validate_frame_header(type, flags, payloadSize);
+ if (status != FrameStatus::goodFrame) {
+ // No need to read any payload.
+ return status;
+ }
+
+ if (Http2PredefinedParameters::maxFrameSize < payloadSize)
+ return FrameStatus::sizeError;
+
+ framePayload.resize(payloadSize);
+ offset = 0;
+ }
+
+ if (framePayload.size()) {
+ if (!readPayload(socket))
+ return FrameStatus::incompleteFrame;
+ }
+
+ return validate_frame_payload(type, flags, framePayload);
+}
+
+bool FrameReader::padded(uchar *pad) const
+{
+ Q_ASSERT(pad);
+
+ if (!flags.testFlag(FrameFlag::PADDED))
+ return false;
+
+ if (type == FrameType::DATA
+ || type == FrameType::PUSH_PROMISE
+ || type == FrameType::HEADERS) {
+ Q_ASSERT(framePayload.size() >= 1);
+ *pad = framePayload[0];
+ return true;
+ }
+
+ return false;
+}
+
+bool FrameReader::priority(quint32 *streamID, uchar *weight) const
+{
+ Q_ASSERT(streamID);
+ Q_ASSERT(weight);
+
+ if (!framePayload.size())
+ return false;
+
+ const uchar *src = &framePayload[0];
+ if (type == FrameType::HEADERS && flags.testFlag(FrameFlag::PADDED))
+ ++src;
+
+ if ((type == FrameType::HEADERS && flags.testFlag(FrameFlag::PRIORITY))
+ || type == FrameType::PRIORITY) {
+ *streamID = qFromBigEndian<quint32>(src);
+ *weight = src[4];
+ return true;
+ }
+
+ return false;
+}
+
+quint32 FrameReader::dataSize() const
+{
+ quint32 size = quint32(framePayload.size());
+ uchar pad = 0;
+ if (padded(&pad)) {
+ // + 1 one for a byte with padding number itself:
+ size -= pad + 1;
+ }
+
+ quint32 dummyID = 0;
+ uchar dummyW = 0;
+ if (priority(&dummyID, &dummyW))
+ size -= 5;
+
+ return size;
+}
+
+const uchar *FrameReader::dataBegin() const
+{
+ if (!framePayload.size())
+ return nullptr;
+
+ const uchar *src = &framePayload[0];
+ uchar dummyPad = 0;
+ if (padded(&dummyPad))
+ ++src;
+
+ quint32 dummyID = 0;
+ uchar dummyW = 0;
+ if (priority(&dummyID, &dummyW))
+ src += 5;
+
+ return src;
+}
+
+bool FrameReader::readHeader(QAbstractSocket &socket)
+{
+ if (socket.bytesAvailable() < frameHeaderSize)
+ return false;
+
+ uchar src[frameHeaderSize] = {};
+ socket.read(reinterpret_cast<char*>(src), frameHeaderSize);
+
+ payloadSize = src[0] << 16 | src[1] << 8 | src[2];
+
+ type = FrameType(src[3]);
+ if (int(type) >= int(FrameType::LAST_FRAME_TYPE))
+ type = FrameType::LAST_FRAME_TYPE; // To be ignored, 5.1
+
+ flags = FrameFlags(src[4]);
+ streamID = qFromBigEndian<quint32>(src + 5);
+
+ return true;
+}
+
+bool FrameReader::readPayload(QAbstractSocket &socket)
+{
+ Q_ASSERT(offset <= framePayload.size());
+
+ // Casts and ugliness - to deal with MSVC. Values are guaranteed to fit into quint32.
+ if (const auto residue = std::min(qint64(framePayload.size() - offset), socket.bytesAvailable())) {
+ socket.read(reinterpret_cast<char *>(&framePayload[offset]), residue);
+ offset += quint32(residue);
+ }
+
+ incompleteRead = offset < framePayload.size();
+ return !incompleteRead;
+}
+
+
+FrameWriter::FrameWriter()
+{
+ frameBuffer.reserve(Http2PredefinedParameters::maxFrameSize +
+ Http2PredefinedParameters::frameHeaderSize);
+}
+
+FrameWriter::FrameWriter(FrameType type, FrameFlags flags, quint32 streamID)
+{
+ frameBuffer.reserve(Http2PredefinedParameters::maxFrameSize +
+ Http2PredefinedParameters::frameHeaderSize);
+ start(type, flags, streamID);
+}
+
+void FrameWriter::start(FrameType type, FrameFlags flags, quint32 streamID)
+{
+ frameBuffer.resize(frameHeaderSize);
+ // The first three bytes - payload size, which is 0 for now.
+ frameBuffer[0] = 0;
+ frameBuffer[1] = 0;
+ frameBuffer[2] = 0;
+
+ frameBuffer[3] = uchar(type);
+ frameBuffer[4] = uchar(flags);
+
+ qToBigEndian(streamID, &frameBuffer[5]);
+}
+
+void FrameWriter::setPayloadSize(quint32 size)
+{
+ Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+ Q_ASSERT(size < maxPayloadSize);
+
+ frameBuffer[0] = size >> 16;
+ frameBuffer[1] = size >> 8;
+ frameBuffer[2] = size;
+}
+
+quint32 FrameWriter::payloadSize() const
+{
+ Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+ return frameBuffer[0] << 16 | frameBuffer[1] << 8 | frameBuffer[2];
+}
+
+void FrameWriter::setType(FrameType type)
+{
+ Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+ frameBuffer[3] = uchar(type);
+}
+
+FrameType FrameWriter::type() const
+{
+ Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+ return FrameType(frameBuffer[3]);
+}
+
+void FrameWriter::setFlags(FrameFlags flags)
+{
+ Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+ frameBuffer[4] = uchar(flags);
+}
+
+void FrameWriter::addFlag(FrameFlag flag)
+{
+ setFlags(flags() | flag);
+}
+
+FrameFlags FrameWriter::flags() const
+{
+ Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+ return FrameFlags(frameBuffer[4]);
+}
+
+quint32 FrameWriter::streamID() const
+{
+ return qFromBigEndian<quint32>(&frameBuffer[5]);
+}
+
+void FrameWriter::append(uchar val)
+{
+ frameBuffer.push_back(val);
+ updatePayloadSize();
+}
+
+void FrameWriter::append(const uchar *begin, const uchar *end)
+{
+ Q_ASSERT(begin && end);
+ Q_ASSERT(begin < end);
+
+ frameBuffer.insert(frameBuffer.end(), begin, end);
+ updatePayloadSize();
+}
+
+void FrameWriter::updatePayloadSize()
+{
+ // First, compute size:
+ const quint32 payloadSize = quint32(frameBuffer.size() - frameHeaderSize);
+ Q_ASSERT(payloadSize <= maxPayloadSize);
+ setPayloadSize(payloadSize);
+}
+
+bool FrameWriter::write(QAbstractSocket &socket) const
+{
+ Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+ // Do some sanity check first:
+ Q_ASSERT(int(type()) < int(FrameType::LAST_FRAME_TYPE));
+ Q_ASSERT(validate_frame_header(type(), flags(), payloadSize()) == FrameStatus::goodFrame);
+
+ const auto nWritten = socket.write(reinterpret_cast<const char *>(&frameBuffer[0]),
+ frameBuffer.size());
+ return nWritten != -1 && size_type(nWritten) == frameBuffer.size();
+}
+
+bool FrameWriter::writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit)
+{
+ Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+
+ if (sizeLimit > quint32(maxPayloadSize))
+ sizeLimit = quint32(maxPayloadSize);
+
+ if (quint32(frameBuffer.size() - frameHeaderSize) <= sizeLimit) {
+ updatePayloadSize();
+ return write(socket);
+ }
+
+ // Write a frame's header (not controlled by sizeLimit) and
+ // as many bytes of payload as we can within sizeLimit,
+ // then send CONTINUATION frames, as needed.
+ setPayloadSize(sizeLimit);
+ const quint32 firstChunkSize = frameHeaderSize + sizeLimit;
+ qint64 written = socket.write(reinterpret_cast<const char *>(&frameBuffer[0]),
+ firstChunkSize);
+
+ if (written != qint64(firstChunkSize))
+ return false;
+
+ FrameWriter continuationFrame(FrameType::CONTINUATION, FrameFlag::EMPTY, streamID());
+ quint32 offset = firstChunkSize;
+
+ while (offset != frameBuffer.size()) {
+ const auto chunkSize = std::min(sizeLimit, quint32(frameBuffer.size() - offset));
+ if (chunkSize + offset == frameBuffer.size())
+ continuationFrame.addFlag(FrameFlag::END_HEADERS);
+ continuationFrame.setPayloadSize(chunkSize);
+ if (!continuationFrame.write(socket))
+ return false;
+ written = socket.write(reinterpret_cast<const char *>(&frameBuffer[offset]),
+ chunkSize);
+ if (written != qint64(chunkSize))
+ return false;
+
+ offset += chunkSize;
+ }
+
+ return true;
+}
+
+bool FrameWriter::writeDATA(QAbstractSocket &socket, quint32 sizeLimit,
+ const uchar *src, quint32 size)
+{
+ // With DATA frame(s) we always have:
+ // 1) frame's header (9 bytes)
+ // 2) a separate payload (from QNonContiguousByteDevice).
+ // We either fit within a sizeLimit, or split into several
+ // DATA frames.
+
+ Q_ASSERT(src);
+
+ if (sizeLimit > quint32(maxPayloadSize))
+ sizeLimit = quint32(maxPayloadSize);
+ // We NEVER set END_STREAM, since QHttp2ProtocolHandler works with
+ // QNonContiguousByteDevice and this 'writeDATA' is probably
+ // not the last one for a given request.
+ // This has to be done externally (sending an empty DATA frame with END_STREAM).
+ for (quint32 offset = 0; offset != size;) {
+ const auto chunkSize = std::min(size - offset, sizeLimit);
+ setPayloadSize(chunkSize);
+ // Frame's header first:
+ if (!write(socket))
+ return false;
+ // Payload (if any):
+ if (chunkSize) {
+ const auto written = socket.write(reinterpret_cast<const char*>(src + offset),
+ chunkSize);
+ if (written != qint64(chunkSize))
+ return false;
+ }
+
+ offset += chunkSize;
+ }
+
+ return true;
+}
+
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/http2/http2frames_p.h b/src/network/access/http2/http2frames_p.h
new file mode 100644
index 0000000000..860555c180
--- /dev/null
+++ b/src/network/access/http2/http2frames_p.h
@@ -0,0 +1,173 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef HTTP2FRAMES_P_H
+#define HTTP2FRAMES_P_H
+
+#include "http2protocol_p.h"
+#include "hpack_p.h"
+
+#include <QtCore/qendian.h>
+#include <QtCore/qglobal.h>
+
+#include <algorithm>
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+
+class QHttp2ProtocolHandler;
+class QAbstractSocket;
+
+namespace Http2
+{
+
+class Q_AUTOTEST_EXPORT FrameReader
+{
+ friend class QT_PREPEND_NAMESPACE(QHttp2ProtocolHandler);
+
+public:
+ FrameReader() = default;
+
+ FrameReader(const FrameReader &) = default;
+ FrameReader(FrameReader &&rhs);
+
+ FrameReader &operator = (const FrameReader &) = default;
+ FrameReader &operator = (FrameReader &&rhs);
+
+ FrameStatus read(QAbstractSocket &socket);
+
+ bool padded(uchar *pad) const;
+ bool priority(quint32 *streamID, uchar *weight) const;
+
+ // N of bytes without padding and/or priority
+ quint32 dataSize() const;
+ // Beginning of payload without priority/padding
+ // bytes.
+ const uchar *dataBegin() const;
+
+ FrameType type = FrameType::LAST_FRAME_TYPE;
+ FrameFlags flags = FrameFlag::EMPTY;
+ quint32 streamID = 0;
+ quint32 payloadSize = 0;
+
+private:
+ bool readHeader(QAbstractSocket &socket);
+ bool readPayload(QAbstractSocket &socket);
+
+ // As soon as we got a header, we
+ // know payload size, offset is
+ // needed if we do not have enough
+ // data and will read the next chunk.
+ bool incompleteRead = false;
+ quint32 offset = 0;
+ std::vector<uchar> framePayload;
+};
+
+class Q_AUTOTEST_EXPORT FrameWriter
+{
+ friend class QT_PREPEND_NAMESPACE(QHttp2ProtocolHandler);
+
+public:
+ using payload_type = std::vector<uchar>;
+ using size_type = payload_type::size_type;
+
+ FrameWriter();
+ FrameWriter(FrameType type, FrameFlags flags, quint32 streamID);
+
+ void start(FrameType type, FrameFlags flags, quint32 streamID);
+
+ void setPayloadSize(quint32 size);
+ quint32 payloadSize() const;
+
+ void setType(FrameType type);
+ FrameType type() const;
+
+ void setFlags(FrameFlags flags);
+ void addFlag(FrameFlag flag);
+ FrameFlags flags() const;
+
+ quint32 streamID() const;
+
+ // All append functions also update frame's payload
+ // length.
+ template<typename ValueType>
+ void append(ValueType val)
+ {
+ uchar wired[sizeof val] = {};
+ qToBigEndian(val, wired);
+ append(wired, wired + sizeof val);
+ }
+ void append(uchar val);
+ void append(Settings identifier)
+ {
+ append(quint16(identifier));
+ }
+ void append(const payload_type &payload)
+ {
+ append(&payload[0], &payload[0] + payload.size());
+ }
+
+ void append(const uchar *begin, const uchar *end);
+
+ // Write 'frameBuffer' as a single frame:
+ bool write(QAbstractSocket &socket) const;
+ // Write as a single frame if we can, or write headers and
+ // CONTINUATION(s) frame(s).
+ bool writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit);
+ // Write either a single DATA frame or several DATA frames
+ // depending on 'sizeLimit'. Data itself is 'external' to
+ // FrameWriter, since it's a 'readPointer' from QNonContiguousData.
+ bool writeDATA(QAbstractSocket &socket, quint32 sizeLimit,
+ const uchar *src, quint32 size);
+
+ std::vector<uchar> &rawFrameBuffer()
+ {
+ return frameBuffer;
+ }
+
+private:
+ void updatePayloadSize();
+ std::vector<uchar> frameBuffer;
+};
+
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/http2/http2protocol.cpp b/src/network/access/http2/http2protocol.cpp
new file mode 100644
index 0000000000..7f788a6f42
--- /dev/null
+++ b/src/network/access/http2/http2protocol.cpp
@@ -0,0 +1,156 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qstring.h>
+
+#include "http2protocol_p.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(QT_HTTP2, "qt.network.http2")
+
+namespace Http2
+{
+
+// 3.5 HTTP/2 Connection Preface:
+// "That is, the connection preface starts with the string
+// PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n)."
+const char Http2clientPreface[clientPrefaceLength] =
+ {0x50, 0x52, 0x49, 0x20, 0x2a, 0x20,
+ 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
+ 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a,
+ 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
+
+
+void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error,
+ QString &errorMessage)
+{
+ if (errorCode > quint32(HTTP_1_1_REQUIRED)) {
+ error = QNetworkReply::ProtocolFailure;
+ errorMessage = QLatin1String("RST_STREAM with unknown error code (%1)");
+ errorMessage = errorMessage.arg(errorCode);
+ return;
+ }
+
+ const Http2Error http2Error = Http2Error(errorCode);
+
+ switch (http2Error) {
+ case HTTP2_NO_ERROR:
+ error = QNetworkReply::NoError;
+ errorMessage.clear();
+ break;
+ case PROTOCOL_ERROR:
+ error = QNetworkReply::ProtocolFailure;
+ errorMessage = QLatin1String("HTTP/2 protocol error");
+ break;
+ case INTERNAL_ERROR:
+ error = QNetworkReply::InternalServerError;
+ errorMessage = QLatin1String("Internal server error");
+ break;
+ case FLOW_CONTROL_ERROR:
+ error = QNetworkReply::ProtocolFailure;
+ errorMessage = QLatin1String("Flow control error");
+ break;
+ case SETTINGS_TIMEOUT:
+ error = QNetworkReply::TimeoutError;
+ errorMessage = QLatin1String("SETTINGS ACK timeout error");
+ break;
+ case STREAM_CLOSED:
+ error = QNetworkReply::ProtocolFailure;
+ errorMessage = QLatin1String("Server received frame(s) on a half-closed stream");
+ break;
+ case FRAME_SIZE_ERROR:
+ error = QNetworkReply::ProtocolFailure;
+ errorMessage = QLatin1String("Server received a frame with an invalid size");
+ break;
+ case REFUSE_STREAM:
+ error = QNetworkReply::ProtocolFailure;
+ errorMessage = QLatin1String("Server refused a stream");
+ break;
+ case CANCEL:
+ error = QNetworkReply::ProtocolFailure;
+ errorMessage = QLatin1String("Stream is no longer needed");
+ break;
+ case COMPRESSION_ERROR:
+ error = QNetworkReply::ProtocolFailure;
+ errorMessage = QLatin1String("Server is unable to maintain the "
+ "header compression context for the connection");
+ break;
+ case CONNECT_ERROR:
+ // TODO: in Qt6 we'll have to add more error codes in QNetworkReply.
+ error = QNetworkReply::UnknownNetworkError;
+ errorMessage = QLatin1String("The connection established in response "
+ "to a CONNECT request was reset or abnormally closed");
+ break;
+ case ENHANCE_YOUR_CALM:
+ error = QNetworkReply::UnknownServerError;
+ errorMessage = QLatin1String("Server dislikes our behavior, excessive load detected.");
+ break;
+ case INADEQUATE_SECURITY:
+ error = QNetworkReply::ContentAccessDenied;
+ errorMessage = QLatin1String("The underlying transport has properties "
+ "that do not meet minimum security "
+ "requirements");
+ break;
+ case HTTP_1_1_REQUIRED:
+ error = QNetworkReply::ProtocolFailure;
+ errorMessage = QLatin1String("Server requires that HTTP/1.1 "
+ "be used instead of HTTP/2.");
+ }
+}
+
+QString qt_error_string(quint32 errorCode)
+{
+ QNetworkReply::NetworkError error = QNetworkReply::NoError;
+ QString message;
+ qt_error(errorCode, error, message);
+ return message;
+}
+
+QNetworkReply::NetworkError qt_error(quint32 errorCode)
+{
+ QNetworkReply::NetworkError error = QNetworkReply::NoError;
+ QString message;
+ qt_error(errorCode, error, message);
+ return error;
+}
+
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/http2/http2protocol_p.h b/src/network/access/http2/http2protocol_p.h
new file mode 100644
index 0000000000..c7088e2179
--- /dev/null
+++ b/src/network/access/http2/http2protocol_p.h
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef HTTP2PROTOCOL_P_H
+#define HTTP2PROTOCOL_P_H
+
+#include <QtNetwork/qnetworkreply.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qglobal.h>
+
+// Different HTTP/2 constants/values as defined by RFC 7540.
+
+QT_BEGIN_NAMESPACE
+
+class QString;
+
+namespace Http2
+{
+
+enum class Settings : quint16
+{
+ HEADER_TABLE_SIZE_ID = 0x1,
+ ENABLE_PUSH_ID = 0x2,
+ MAX_CONCURRENT_STREAMS_ID = 0x3,
+ INITIAL_WINDOW_SIZE_ID = 0x4,
+ MAX_FRAME_SIZE_ID = 0x5,
+ MAX_HEADER_LIST_SIZE_ID = 0x6
+};
+
+enum class FrameType : uchar
+{
+ DATA = 0x0,
+ HEADERS = 0x1,
+ PRIORITY = 0x2,
+ RST_STREAM = 0x3,
+ SETTINGS = 0x4,
+ PUSH_PROMISE = 0x5,
+ PING = 0x6,
+ GOAWAY = 0x7,
+ WINDOW_UPDATE = 0x8,
+ CONTINUATION = 0x9,
+ // ATTENTION: enumerators must be sorted.
+ // We use LAST_FRAME_TYPE to check if
+ // frame type is known, if not - this frame
+ // must be ignored, HTTP/2 5.1).
+ LAST_FRAME_TYPE
+};
+
+enum class FrameFlag : uchar
+{
+ EMPTY = 0x0, // Valid for any frame type.
+ ACK = 0x1, // Valid for PING, SETTINGS
+ END_STREAM = 0x1, // Valid for HEADERS, DATA
+ END_HEADERS = 0x4, // Valid for PUSH_PROMISE, HEADERS,
+ PADDED = 0x8, // Valid for PUSH_PROMISE, HEADERS, DATA
+ PRIORITY = 0x20 // Valid for HEADERS,
+};
+
+Q_DECLARE_FLAGS(FrameFlags, FrameFlag)
+Q_DECLARE_OPERATORS_FOR_FLAGS(FrameFlags)
+
+enum Http2PredefinedParameters
+{
+ // Old-style enum, so we
+ // can use as Http2::frameHeaderSize for example.
+ clientPrefaceLength = 24, // HTTP/2, 3.5
+ connectionStreamID = 0, // HTTP/2, 5.1.1
+ frameHeaderSize = 9, // HTTP/2, 4.1
+
+ // It's our max frame size we send in SETTINGS frame,
+ // it's also the default one and we also use it to later
+ // validate incoming frames:
+ maxFrameSize = 16384, // HTTP/2 6.5.2
+
+ defaultSessionWindowSize = 65535, // HTTP/2 6.5.2
+ maxPayloadSize = (1 << 24) - 1, // HTTP/2 6.5.2
+ // Using 1000 (rather arbitrarily), just to
+ // impose *some* upper limit:
+ maxPeerConcurrentStreams = 1000,
+ maxConcurrentStreams = 100 // HTTP/2, 6.5.2
+};
+
+extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength];
+
+enum class FrameStatus
+{
+ protocolError,
+ sizeError,
+ incompleteFrame,
+ goodFrame
+};
+
+enum Http2Error
+{
+ // Old-style enum to avoid excessive name
+ // qualification ...
+ // NB:
+ // I use the last enumerator to check
+ // that errorCode (quint32) is valid,
+ // so it needs to be the highest-numbered!
+ // HTTP/2 7:
+ HTTP2_NO_ERROR = 0x0,
+ PROTOCOL_ERROR = 0x1,
+ INTERNAL_ERROR = 0x2,
+ FLOW_CONTROL_ERROR = 0x3,
+ SETTINGS_TIMEOUT = 0x4,
+ STREAM_CLOSED = 0x5,
+ FRAME_SIZE_ERROR = 0x6,
+ REFUSE_STREAM = 0x7,
+ CANCEL = 0x8,
+ COMPRESSION_ERROR = 0x9,
+ CONNECT_ERROR = 0xa,
+ ENHANCE_YOUR_CALM = 0xb,
+ INADEQUATE_SECURITY = 0xc,
+ HTTP_1_1_REQUIRED = 0xd
+};
+
+void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, QString &errorString);
+QString qt_error_string(quint32 errorCode);
+QNetworkReply::NetworkError qt_error(quint32 errorCode);
+
+}
+
+Q_DECLARE_LOGGING_CATEGORY(QT_HTTP2)
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/http2/http2streams.cpp b/src/network/access/http2/http2streams.cpp
new file mode 100644
index 0000000000..f57f8d8367
--- /dev/null
+++ b/src/network/access/http2/http2streams.cpp
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "http2streams_p.h"
+
+#include "private/qhttp2protocolhandler_p.h"
+#include "private/qhttpnetworkreply_p.h"
+
+#include <QtCore/qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Http2
+{
+
+Stream::Stream(const HttpMessagePair &message, quint32 id, qint32 sendSize, qint32 recvSize)
+ : httpPair(message),
+ streamID(id),
+ sendWindow(sendSize),
+ recvWindow(recvSize)
+{
+}
+
+QHttpNetworkReply *Stream::reply() const
+{
+ return httpPair.second;
+}
+
+const QHttpNetworkRequest &Stream::request() const
+{
+ return httpPair.first;
+}
+
+QHttpNetworkRequest &Stream::request()
+{
+ return httpPair.first;
+}
+
+QHttpNetworkRequest::Priority Stream::priority() const
+{
+ return httpPair.first.priority();
+}
+
+uchar Stream::weight() const
+{
+ switch (priority()) {
+ case QHttpNetworkRequest::LowPriority:
+ return 0;
+ case QHttpNetworkRequest::NormalPriority:
+ return 127;
+ case QHttpNetworkRequest::HighPriority:
+ default:
+ return 255;
+ }
+}
+
+QNonContiguousByteDevice *Stream::data() const
+{
+ return httpPair.first.uploadByteDevice();
+}
+
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/http2/http2streams_p.h b/src/network/access/http2/http2streams_p.h
new file mode 100644
index 0000000000..7db5d21694
--- /dev/null
+++ b/src/network/access/http2/http2streams_p.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef HTTP2STREAMS_P_H
+#define HTTP2STREAMS_P_H
+
+#include <private/qhttpnetworkconnectionchannel_p.h>
+#include <private/qhttpnetworkrequest_p.h>
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QNonContiguousByteDevice;
+
+namespace Http2
+{
+
+struct Q_AUTOTEST_EXPORT Stream
+{
+ enum StreamState {
+ idle,
+ open,
+ halfClosedLocal,
+ halfClosedRemote,
+ closed
+ };
+
+ Stream() = default;
+ Stream(const HttpMessagePair &message, quint32 streamID, qint32 sendSize,
+ qint32 recvSize);
+
+ // TODO: check includes!!!
+ QHttpNetworkReply *reply() const;
+ const QHttpNetworkRequest &request() const;
+ QHttpNetworkRequest &request();
+ QHttpNetworkRequest::Priority priority() const;
+ uchar weight() const;
+
+ QNonContiguousByteDevice *data() const;
+
+ HttpMessagePair httpPair;
+ quint32 streamID = 0;
+ // Signed as window sizes can become negative:
+ qint32 sendWindow = 65535;
+ qint32 recvWindow = 65535;
+
+ StreamState state = idle;
+};
+
+}
+
+QT_END_NAMESPACE
+
+#endif
+
diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp
new file mode 100644
index 0000000000..6a0aae9693
--- /dev/null
+++ b/src/network/access/qhttp2protocolhandler.cpp
@@ -0,0 +1,1219 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#define NOMINMAX
+
+#include "qhttpnetworkconnection_p.h"
+#include "qhttp2protocolhandler_p.h"
+
+#if !defined(QT_NO_HTTP) && !defined(QT_NO_SSL)
+
+#include "http2/bitstreams_p.h"
+
+#include <private/qnoncontiguousbytedevice_p.h>
+
+#include <QtNetwork/qabstractsocket.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qendian.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qurl.h>
+
+#include <algorithm>
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+
+namespace
+{
+
+HPack::HttpHeader build_headers(const QHttpNetworkRequest &request, quint32 maxHeaderListSize)
+{
+ using namespace HPack;
+
+ HttpHeader header;
+ header.reserve(300);
+
+ // 1. Before anything - mandatory fields, if they do not fit into maxHeaderList -
+ // then stop immediately with error.
+ const auto auth = request.url().authority(QUrl::FullyEncoded | QUrl::RemoveUserInfo).toLatin1();
+ header.push_back(HeaderField(":authority", auth));
+ header.push_back(HeaderField(":method", request.methodName()));
+ header.push_back(HeaderField(":path", request.uri(false)));
+ header.push_back(HeaderField(":scheme", request.url().scheme().toLatin1()));
+
+ HeaderSize size = header_size(header);
+ if (!size.first) // Ooops!
+ return HttpHeader();
+
+ if (size.second > maxHeaderListSize)
+ return HttpHeader(); // Bad, we cannot send this request ...
+
+ for (const auto &field : request.header()) {
+ const HeaderSize delta = entry_size(field.first, field.second);
+ if (!delta.first) // Overflow???
+ break;
+ if (std::numeric_limits<quint32>::max() - delta.second < size.second)
+ break;
+ size.second += delta.second;
+ if (size.second > maxHeaderListSize)
+ break;
+
+ QByteArray key(field.first.toLower());
+ if (key == "connection" || key == "host" || key == "keep-alive"
+ || key == "proxy-connection" || key == "transfer-encoding")
+ continue; // Those headers are not valid (section 3.2.1) - from QSpdyProtocolHandler
+ // TODO: verify with specs, which fields are valid to send ....
+ // toLower - 8.1.2 .... "header field names MUST be converted to lowercase prior
+ // to their encoding in HTTP/2.
+ // A request or response containing uppercase header field names
+ // MUST be treated as malformed (Section 8.1.2.6)".
+ header.push_back(HeaderField(key, field.second));
+ }
+
+ return header;
+}
+
+bool sum_will_overflow(qint32 windowSize, qint32 delta)
+{
+ if (windowSize > 0)
+ return std::numeric_limits<qint32>::max() - windowSize < delta;
+ return std::numeric_limits<qint32>::min() - windowSize > delta;
+}
+
+}// Unnamed namespace
+
+// Since we anyway end up having this in every function definition:
+using namespace Http2;
+
+const std::deque<quint32>::size_type QHttp2ProtocolHandler::maxRecycledStreams = 10000;
+const qint32 QHttp2ProtocolHandler::sessionMaxRecvWindowSize;
+const qint32 QHttp2ProtocolHandler::streamInitialRecvWindowSize;
+const quint32 QHttp2ProtocolHandler::maxAcceptableTableSize;
+
+QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *channel)
+ : QAbstractProtocolHandler(channel),
+ decoder(HPack::FieldLookupTable::DefaultSize),
+ encoder(HPack::FieldLookupTable::DefaultSize, true)
+{
+ continuedFrames.reserve(20);
+}
+
+void QHttp2ProtocolHandler::_q_uploadDataReadyRead()
+{
+ auto data = qobject_cast<QNonContiguousByteDevice *>(sender());
+ Q_ASSERT(data);
+ const qint32 streamID = data->property("HTTP2StreamID").toInt();
+ Q_ASSERT(activeStreams.contains(streamID));
+ auto &stream = activeStreams[streamID];
+
+ if (!sendDATA(stream)) {
+ finishStreamWithError(stream, QNetworkReply::UnknownNetworkError,
+ QLatin1String("failed to send DATA"));
+ sendRST_STREAM(streamID, INTERNAL_ERROR);
+ markAsReset(streamID);
+ deleteActiveStream(streamID);
+ }
+}
+
+void QHttp2ProtocolHandler::_q_replyDestroyed(QObject *reply)
+{
+ const quint32 streamID = reply->property("HTTP2StreamID").toInt();
+ if (activeStreams.contains(streamID)) {
+ sendRST_STREAM(streamID, CANCEL);
+ markAsReset(streamID);
+ deleteActiveStream(streamID);
+ }
+}
+
+void QHttp2ProtocolHandler::_q_readyRead()
+{
+ _q_receiveReply();
+}
+
+void QHttp2ProtocolHandler::_q_receiveReply()
+{
+ Q_ASSERT(m_socket);
+ Q_ASSERT(m_channel);
+
+ const auto result = inboundFrame.read(*m_socket);
+ switch (result) {
+ case FrameStatus::incompleteFrame:
+ return;
+ case FrameStatus::protocolError:
+ return connectionError(PROTOCOL_ERROR, "invalid frame");
+ case FrameStatus::sizeError:
+ return connectionError(FRAME_SIZE_ERROR, "invalid frame size");
+ default:
+ break;
+ }
+
+ Q_ASSERT(result == FrameStatus::goodFrame);
+
+ if (continuationExpected && inboundFrame.type != FrameType::CONTINUATION)
+ return connectionError(PROTOCOL_ERROR, "CONTINUATION expected");
+
+ switch (inboundFrame.type) {
+ case FrameType::DATA:
+ handleDATA();
+ break;
+ case FrameType::HEADERS:
+ handleHEADERS();
+ break;
+ case FrameType::PRIORITY:
+ handlePRIORITY();
+ break;
+ case FrameType::RST_STREAM:
+ handleRST_STREAM();
+ break;
+ case FrameType::SETTINGS:
+ handleSETTINGS();
+ break;
+ case FrameType::PUSH_PROMISE:
+ handlePUSH_PROMISE();
+ break;
+ case FrameType::PING:
+ handlePING();
+ break;
+ case FrameType::GOAWAY:
+ handleGOAWAY();
+ break;
+ case FrameType::WINDOW_UPDATE:
+ handleWINDOW_UPDATE();
+ break;
+ case FrameType::CONTINUATION:
+ handleCONTINUATION();
+ break;
+ case FrameType::LAST_FRAME_TYPE:
+ // 5.1 - ignore unknown frames.
+ break;
+ }
+
+ if (goingAway && !activeStreams.size())
+ return;
+
+ if (m_socket->bytesAvailable())
+ QMetaObject::invokeMethod(m_channel, "_q_receiveReply", Qt::QueuedConnection);
+}
+
+bool QHttp2ProtocolHandler::sendRequest()
+{
+ if (goingAway)
+ return false;
+
+ if (!prefaceSent && !sendClientPreface())
+ return false;
+
+ auto &requests = m_channel->spdyRequestsToSend;
+ if (!requests.size())
+ return true;
+
+ const auto streamsToUse = std::min<quint32>(maxConcurrentStreams - activeStreams.size(),
+ requests.size());
+ auto it = requests.begin();
+ m_channel->state = QHttpNetworkConnectionChannel::WritingState;
+ for (quint32 i = 0; i < streamsToUse; ++i) {
+ const qint32 newStreamID = createNewStream(*it);
+ if (!newStreamID) {
+ // TODO: actually we have to open a new connection.
+ qCCritical(QT_HTTP2, "sendRequest: out of stream IDs");
+ break;
+ }
+
+ it = requests.erase(it);
+
+ Stream &newStream = activeStreams[newStreamID];
+ if (!sendHEADERS(newStream)) {
+ finishStreamWithError(newStream, QNetworkReply::UnknownNetworkError,
+ QLatin1String("failed to send HEADERS frame(s)"));
+ deleteActiveStream(newStreamID);
+ continue;
+ }
+
+ if (newStream.data() && !sendDATA(newStream)) {
+ finishStreamWithError(newStream, QNetworkReply::UnknownNetworkError,
+ QLatin1String("failed to send DATA frame(s)"));
+ sendRST_STREAM(newStreamID, INTERNAL_ERROR);
+ markAsReset(newStreamID);
+ deleteActiveStream(newStreamID);
+ }
+ }
+
+ m_channel->state = QHttpNetworkConnectionChannel::IdleState;
+
+ return true;
+}
+
+
+bool QHttp2ProtocolHandler::sendClientPreface()
+{
+ // 3.5 HTTP/2 Connection Preface
+ Q_ASSERT(m_socket);
+
+ if (prefaceSent)
+ return true;
+
+ const qint64 written = m_socket->write(Http2clientPreface,
+ Http2::clientPrefaceLength);
+ if (written != Http2::clientPrefaceLength)
+ return false;
+
+ // 6.5 SETTINGS
+ outboundFrame.start(FrameType::SETTINGS, FrameFlag::EMPTY, Http2::connectionStreamID);
+ // MAX frame size (16 kb), disable PUSH
+ outboundFrame.append(Settings::MAX_FRAME_SIZE_ID);
+ outboundFrame.append(quint32(Http2::maxFrameSize));
+ outboundFrame.append(Settings::ENABLE_PUSH_ID);
+ outboundFrame.append(quint32(0));
+
+ if (!outboundFrame.write(*m_socket))
+ return false;
+
+ sessionRecvWindowSize = sessionMaxRecvWindowSize;
+ if (defaultSessionWindowSize < sessionMaxRecvWindowSize) {
+ const auto delta = sessionMaxRecvWindowSize - defaultSessionWindowSize;
+ if (!sendWINDOW_UPDATE(connectionStreamID, delta))
+ return false;
+ }
+
+ prefaceSent = true;
+ waitingForSettingsACK = true;
+
+ return true;
+}
+
+bool QHttp2ProtocolHandler::sendSETTINGS_ACK()
+{
+ Q_ASSERT(m_socket);
+
+ if (!prefaceSent && !sendClientPreface())
+ return false;
+
+ outboundFrame.start(FrameType::SETTINGS, FrameFlag::ACK, Http2::connectionStreamID);
+
+ return outboundFrame.write(*m_socket);
+}
+
+bool QHttp2ProtocolHandler::sendHEADERS(Stream &stream)
+{
+ using namespace HPack;
+
+ outboundFrame.start(FrameType::HEADERS, FrameFlag::PRIORITY | FrameFlag::END_HEADERS,
+ stream.streamID);
+
+ if (!stream.data()) {
+ outboundFrame.addFlag(FrameFlag::END_STREAM);
+ stream.state = Stream::halfClosedLocal;
+ } else {
+ stream.state = Stream::open;
+ }
+
+ outboundFrame.append(quint32()); // No stream dependency in Qt.
+ outboundFrame.append(stream.weight());
+
+ const auto headers = build_headers(stream.request(), maxHeaderListSize);
+ if (!headers.size()) // nothing fits into maxHeaderListSize
+ return false;
+
+ // Compress in-place:
+ BitOStream outputStream(outboundFrame.frameBuffer);
+ if (!encoder.encodeRequest(outputStream, headers))
+ return false;
+
+ return outboundFrame.writeHEADERS(*m_socket, maxFrameSize);
+}
+
+bool QHttp2ProtocolHandler::sendDATA(Stream &stream)
+{
+ Q_ASSERT(maxFrameSize > frameHeaderSize);
+ Q_ASSERT(m_socket);
+ Q_ASSERT(stream.data());
+
+ const auto &request = stream.request();
+ auto reply = stream.reply();
+ Q_ASSERT(reply);
+ const auto replyPrivate = reply->d_func();
+ Q_ASSERT(replyPrivate);
+
+ auto slot = std::min<qint32>(sessionSendWindowSize, stream.sendWindow);
+ while (!stream.data()->atEnd() && slot) {
+ qint64 chunkSize = 0;
+ const uchar *src =
+ reinterpret_cast<const uchar *>(stream.data()->readPointer(slot, chunkSize));
+
+ if (chunkSize == -1)
+ return false;
+
+ if (!src || !chunkSize) {
+ // Stream is not suspended by the flow control,
+ // we do not have data ready yet.
+ return true;
+ }
+
+ outboundFrame.start(FrameType::DATA, FrameFlag::EMPTY, stream.streamID);
+ const qint32 bytesWritten = std::min<qint32>(slot, chunkSize);
+
+ if (!outboundFrame.writeDATA(*m_socket, maxFrameSize, src, bytesWritten))
+ return false;
+
+ stream.data()->advanceReadPointer(bytesWritten);
+ stream.sendWindow -= bytesWritten;
+ sessionSendWindowSize -= bytesWritten;
+ replyPrivate->totallyUploadedData += bytesWritten;
+ emit reply->dataSendProgress(replyPrivate->totallyUploadedData,
+ request.contentLength());
+ slot = std::min(sessionSendWindowSize, stream.sendWindow);
+ }
+
+ if (replyPrivate->totallyUploadedData == request.contentLength()) {
+ outboundFrame.start(FrameType::DATA, FrameFlag::END_STREAM, stream.streamID);
+ outboundFrame.setPayloadSize(0);
+ outboundFrame.write(*m_socket);
+ stream.state = Stream::halfClosedLocal;
+ stream.data()->disconnect(this);
+ removeFromSuspended(stream.streamID);
+ } else if (!stream.data()->atEnd()) {
+ addToSuspended(stream);
+ }
+
+ return true;
+}
+
+bool QHttp2ProtocolHandler::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
+{
+ Q_ASSERT(m_socket);
+
+ outboundFrame.start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID);
+ outboundFrame.append(delta);
+ return outboundFrame.write(*m_socket);
+}
+
+bool QHttp2ProtocolHandler::sendRST_STREAM(quint32 streamID, quint32 errorCode)
+{
+ Q_ASSERT(m_socket);
+
+ outboundFrame.start(FrameType::RST_STREAM, FrameFlag::EMPTY, streamID);
+ outboundFrame.append(errorCode);
+ return outboundFrame.write(*m_socket);
+}
+
+bool QHttp2ProtocolHandler::sendGOAWAY(quint32 errorCode)
+{
+ Q_ASSERT(m_socket);
+
+ outboundFrame.start(FrameType::GOAWAY, FrameFlag::EMPTY, connectionStreamID);
+ outboundFrame.append(quint32(connectionStreamID));
+ outboundFrame.append(errorCode);
+ return outboundFrame.write(*m_socket);
+}
+
+void QHttp2ProtocolHandler::handleDATA()
+{
+ Q_ASSERT(inboundFrame.type == FrameType::DATA);
+
+ const auto streamID = inboundFrame.streamID;
+ if (streamID == connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "DATA on stream 0x0");
+
+ if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
+ return connectionError(ENHANCE_YOUR_CALM, "DATA on invalid stream");
+
+ if (qint32(inboundFrame.payloadSize) > sessionRecvWindowSize)
+ return connectionError(FLOW_CONTROL_ERROR, "Flow control error");
+
+ sessionRecvWindowSize -= inboundFrame.payloadSize;
+
+ if (activeStreams.contains(streamID)) {
+ auto &stream = activeStreams[streamID];
+
+ if (qint32(inboundFrame.payloadSize) > stream.recvWindow) {
+ finishStreamWithError(stream, QNetworkReply::ProtocolInvalidOperationError,
+ QLatin1String("flow control error"));
+ sendRST_STREAM(streamID, FLOW_CONTROL_ERROR);
+ markAsReset(streamID);
+ deleteActiveStream(streamID);
+ } else {
+ stream.recvWindow -= inboundFrame.payloadSize;
+ // Uncompress data if needed and append it ...
+ updateStream(stream, inboundFrame);
+
+ if (inboundFrame.flags.testFlag(FrameFlag::END_STREAM)) {
+ finishStream(stream);
+ deleteActiveStream(stream.streamID);
+ } else if (stream.recvWindow < streamInitialRecvWindowSize / 2) {
+ QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection,
+ Q_ARG(quint32, stream.streamID),
+ Q_ARG(quint32, streamInitialRecvWindowSize - stream.recvWindow));
+ stream.recvWindow = streamInitialRecvWindowSize;
+ }
+ }
+ }
+
+ if (sessionRecvWindowSize < sessionMaxRecvWindowSize / 2) {
+ QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection,
+ Q_ARG(quint32, connectionStreamID),
+ Q_ARG(quint32, sessionMaxRecvWindowSize - sessionRecvWindowSize));
+ sessionRecvWindowSize = sessionMaxRecvWindowSize;
+ }
+}
+
+void QHttp2ProtocolHandler::handleHEADERS()
+{
+ Q_ASSERT(inboundFrame.type == FrameType::HEADERS);
+
+ const auto streamID = inboundFrame.streamID;
+ if (streamID == connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "HEADERS on 0x0 stream");
+
+ if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
+ return connectionError(ENHANCE_YOUR_CALM, "HEADERS on invalid stream");
+
+ if (inboundFrame.flags.testFlag(FrameFlag::PRIORITY)) {
+ handlePRIORITY();
+ if (goingAway)
+ return;
+ }
+
+ const bool endHeaders = inboundFrame.flags.testFlag(FrameFlag::END_HEADERS);
+ continuedFrames.clear();
+ continuedFrames.push_back(std::move(inboundFrame));
+ if (!endHeaders) {
+ continuationExpected = true;
+ return;
+ }
+
+ handleContinuedHEADERS();
+}
+
+void QHttp2ProtocolHandler::handlePRIORITY()
+{
+ Q_ASSERT(inboundFrame.type == FrameType::PRIORITY ||
+ inboundFrame.type == FrameType::HEADERS);
+
+ const auto streamID = inboundFrame.streamID;
+ if (streamID == connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "PIRORITY on 0x0 stream");
+
+ if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
+ return connectionError(ENHANCE_YOUR_CALM, "PRIORITY on invalid stream");
+
+ quint32 streamDependency = 0;
+ uchar weight = 0;
+ const bool noErr = inboundFrame.priority(&streamDependency, &weight);
+ Q_UNUSED(noErr) Q_ASSERT(noErr);
+
+
+ const bool exclusive = streamDependency & 0x80000000;
+ streamDependency &= ~0x80000000;
+
+ // Ignore this for now ...
+ // Can be used for streams (re)prioritization - 5.3
+ Q_UNUSED(exclusive);
+ Q_UNUSED(weight);
+}
+
+void QHttp2ProtocolHandler::handleRST_STREAM()
+{
+ Q_ASSERT(inboundFrame.type == FrameType::RST_STREAM);
+
+ // "RST_STREAM frames MUST be associated with a stream.
+ // If a RST_STREAM frame is received with a stream identifier of 0x0,
+ // the recipient MUST treat this as a connection error (Section 5.4.1)
+ // of type PROTOCOL_ERROR.
+ if (inboundFrame.streamID == connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "RST_STREAM on 0x0");
+
+ if (!(inboundFrame.streamID & 0x1)) {
+ // RST_STREAM on a promised stream:
+ // since we do not keep track of such streams,
+ // just ignore.
+ return;
+ }
+
+ if (inboundFrame.streamID >= nextID) {
+ // "RST_STREAM frames MUST NOT be sent for a stream
+ // in the "idle" state. .. the recipient MUST treat this
+ // as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
+ return connectionError(PROTOCOL_ERROR, "RST_STREAM on idle stream");
+ }
+
+ if (!activeStreams.contains(inboundFrame.streamID)) {
+ // 'closed' stream, ignore.
+ return;
+ }
+
+ Q_ASSERT(inboundFrame.dataSize() == 4);
+
+ Stream &stream = activeStreams[inboundFrame.streamID];
+ finishStreamWithError(stream, qFromBigEndian<quint32>(inboundFrame.dataBegin()));
+ markAsReset(stream.streamID);
+ deleteActiveStream(stream.streamID);
+}
+
+void QHttp2ProtocolHandler::handleSETTINGS()
+{
+ // 6.5 SETTINGS.
+ Q_ASSERT(inboundFrame.type == FrameType::SETTINGS);
+
+ if (inboundFrame.streamID != connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "SETTINGS on invalid stream");
+
+ if (inboundFrame.flags.testFlag(FrameFlag::ACK)) {
+ if (!waitingForSettingsACK)
+ return connectionError(PROTOCOL_ERROR, "unexpected SETTINGS ACK");
+ waitingForSettingsACK = false;
+ return;
+ }
+
+ if (inboundFrame.dataSize()) {
+ auto src = inboundFrame.dataBegin();
+ for (const uchar *end = src + inboundFrame.dataSize(); src != end; src += 6) {
+ const Settings identifier = Settings(qFromBigEndian<quint16>(src));
+ const quint32 intVal = qFromBigEndian<quint32>(src + 2);
+ if (!acceptSetting(identifier, intVal)) {
+ // If not accepted - we finish with connectionError.
+ return;
+ }
+ }
+ }
+
+ sendSETTINGS_ACK();
+}
+
+
+void QHttp2ProtocolHandler::handlePUSH_PROMISE()
+{
+ // 6.6 PUSH_PROMISE.
+ Q_ASSERT(inboundFrame.type == FrameType::PUSH_PROMISE);
+
+ if (prefaceSent && !waitingForSettingsACK) {
+ // This means, server ACKed our 'NO PUSH',
+ // but sent us PUSH_PROMISE anyway.
+ return connectionError(PROTOCOL_ERROR, "unexpected PUSH_PROMISE frame");
+ }
+
+ const auto streamID = inboundFrame.streamID;
+ if (streamID == connectionStreamID) {
+ return connectionError(PROTOCOL_ERROR,
+ "PUSH_PROMISE with invalid associated stream (0x0)");
+ }
+
+ if (!activeStreams.contains(streamID) && !streamWasReset(streamID)) {
+ return connectionError(ENHANCE_YOUR_CALM,
+ "PUSH_PROMISE with invalid associated stream");
+ }
+
+ const auto reservedID = qFromBigEndian<quint32>(inboundFrame.dataBegin());
+ if (!reservedID || (reservedID & 0x1)) {
+ return connectionError(PROTOCOL_ERROR,
+ "PUSH_PROMISE with invalid promised stream ID");
+ }
+
+ // "ignoring a PUSH_PROMISE frame causes the stream
+ // state to become indeterminate" - let's RST_STREAM it then ...
+ sendRST_STREAM(reservedID, REFUSE_STREAM);
+ markAsReset(reservedID);
+
+ const bool endHeaders = inboundFrame.flags.testFlag(FrameFlag::END_HEADERS);
+ continuedFrames.clear();
+ continuedFrames.push_back(std::move(inboundFrame));
+
+ if (!endHeaders) {
+ continuationExpected = true;
+ return;
+ }
+
+ handleContinuedHEADERS();
+}
+
+void QHttp2ProtocolHandler::handlePING()
+{
+ // Since we're implementing a client and not
+ // a server, we only reply to a PING, ACKing it.
+ Q_ASSERT(inboundFrame.type == FrameType::PING);
+ Q_ASSERT(m_socket);
+
+ if (inboundFrame.streamID != connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "PING on invalid stream");
+
+ if (inboundFrame.flags & FrameFlag::ACK)
+ return connectionError(PROTOCOL_ERROR, "unexpected PING ACK");
+
+ Q_ASSERT(inboundFrame.dataSize() == 8);
+
+ outboundFrame.start(FrameType::PING, FrameFlag::ACK, connectionStreamID);
+ outboundFrame.append(inboundFrame.dataBegin(), inboundFrame.dataBegin() + 8);
+ outboundFrame.write(*m_socket);
+}
+
+void QHttp2ProtocolHandler::handleGOAWAY()
+{
+ // 6.8 GOAWAY
+
+ Q_ASSERT(inboundFrame.type == FrameType::GOAWAY);
+ // "An endpoint MUST treat a GOAWAY frame with a stream identifier
+ // other than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
+ if (inboundFrame.streamID != connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "GOAWAY on invalid stream");
+
+ const auto src = inboundFrame.dataBegin();
+ quint32 lastStreamID = qFromBigEndian<quint32>(src);
+ const quint32 errorCode = qFromBigEndian<quint32>(src + 4);
+
+ if (!lastStreamID) {
+ // "The last stream identifier can be set to 0 if no
+ // streams were processed."
+ lastStreamID = 1;
+ }
+
+ if (!(lastStreamID & 0x1)) {
+ // 5.1.1 - we (client) use only odd numbers as stream identifiers.
+ return connectionError(PROTOCOL_ERROR, "GOAWAY with invalid last stream ID");
+ }
+
+ if (lastStreamID >= nextID) {
+ // "A server that is attempting to gracefully shut down a connection SHOULD
+ // send an initial GOAWAY frame with the last stream identifier set to 2^31-1
+ // and a NO_ERROR code."
+ if (lastStreamID != (quint32(1) << 31) - 1 || errorCode != HTTP2_NO_ERROR)
+ return connectionError(PROTOCOL_ERROR, "GOAWAY invalid stream/error code");
+ lastStreamID = 1;
+ } else {
+ lastStreamID += 2;
+ }
+
+ goingAway = true;
+
+ QNetworkReply::NetworkError error = QNetworkReply::NoError;
+ QString message;
+ qt_error(errorCode, error, message);
+
+ for (quint32 id = lastStreamID; id < nextID; id += 2) {
+ const auto it = activeStreams.find(id);
+ if (it != activeStreams.end()) {
+ Stream &stream = *it;
+ finishStreamWithError(stream, error, message);
+ markAsReset(id);
+ deleteActiveStream(id);
+ } else {
+ removeFromSuspended(id);
+ }
+ }
+
+ if (!activeStreams.size())
+ closeSession();
+}
+
+void QHttp2ProtocolHandler::handleWINDOW_UPDATE()
+{
+ Q_ASSERT(inboundFrame.type == FrameType::WINDOW_UPDATE);
+
+
+ const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
+ const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
+ const auto streamID = inboundFrame.streamID;
+
+ if (streamID == Http2::connectionStreamID) {
+ if (!valid || sum_will_overflow(sessionSendWindowSize, delta))
+ return connectionError(PROTOCOL_ERROR, "WINDOW_UPDATE invalid delta");
+ sessionSendWindowSize += delta;
+ } else {
+ if (!activeStreams.contains(streamID)) {
+ // WINDOW_UPDATE on closed streams can be ignored.
+ return;
+ }
+ auto &stream = activeStreams[streamID];
+ if (!valid || sum_will_overflow(stream.sendWindow, delta)) {
+ finishStreamWithError(stream, QNetworkReply::ProtocolInvalidOperationError,
+ QLatin1String("invalid WINDOW_UPDATE delta"));
+ sendRST_STREAM(streamID, PROTOCOL_ERROR);
+ markAsReset(streamID);
+ deleteActiveStream(streamID);
+ return;
+ }
+ stream.sendWindow += delta;
+ }
+
+ // Since we're in _q_receiveReply at the moment, let's first handle other
+ // frames and resume suspended streams (if any) == start sending our own frame
+ // after handling these frames, since one them can be e.g. GOAWAY.
+ QMetaObject::invokeMethod(this, "resumeSuspendedStreams", Qt::QueuedConnection);
+}
+
+void QHttp2ProtocolHandler::handleCONTINUATION()
+{
+ Q_ASSERT(inboundFrame.type == FrameType::CONTINUATION);
+ Q_ASSERT(continuedFrames.size()); // HEADERS frame must be already in.
+
+ if (inboundFrame.streamID != continuedFrames.front().streamID)
+ return connectionError(PROTOCOL_ERROR, "CONTINUATION on invalid stream");
+
+ const bool endHeaders = inboundFrame.flags.testFlag(FrameFlag::END_HEADERS);
+ continuedFrames.push_back(std::move(inboundFrame));
+
+ if (!endHeaders)
+ return;
+
+ continuationExpected = false;
+ handleContinuedHEADERS();
+}
+
+void QHttp2ProtocolHandler::handleContinuedHEADERS()
+{
+ Q_ASSERT(continuedFrames.size());
+
+ const auto streamID = continuedFrames[0].streamID;
+
+ if (continuedFrames[0].type == FrameType::HEADERS) {
+ if (activeStreams.contains(streamID)) {
+ Stream &stream = activeStreams[streamID];
+ if (stream.state != Stream::halfClosedLocal) {
+ // If we're receiving headers, they're a response to a request we sent;
+ // and we closed our end when we finished sending that.
+ finishStreamWithError(stream, QNetworkReply::ProtocolInvalidOperationError,
+ QLatin1String("HEADERS on invalid stream"));
+ sendRST_STREAM(streamID, CANCEL);
+ markAsReset(streamID);
+ deleteActiveStream(streamID);
+ return;
+ }
+ } else if (!streamWasReset(streamID)) {
+ return connectionError(PROTOCOL_ERROR, "HEADERS on invalid stream");
+ }
+ }
+
+ quint32 total = 0;
+ for (const auto &frame : continuedFrames)
+ total += frame.dataSize();
+
+ if (!total) {
+ // It could be a PRIORITY sent in HEADERS - handled by this point.
+ return;
+ }
+
+ std::vector<uchar> hpackBlock(total);
+ auto dst = hpackBlock.begin();
+ for (const auto &frame : continuedFrames) {
+ if (!frame.dataSize())
+ continue;
+ const uchar *src = frame.dataBegin();
+ std::copy(src, src + frame.dataSize(), dst);
+ dst += frame.dataSize();
+ }
+
+ HPack::BitIStream inputStream{&hpackBlock[0],
+ &hpackBlock[0] + hpackBlock.size()};
+
+ if (!decoder.decodeHeaderFields(inputStream))
+ return connectionError(COMPRESSION_ERROR, "HPACK decompression failed");
+
+ if (continuedFrames[0].type == FrameType::HEADERS) {
+ if (activeStreams.contains(streamID)) {
+ Stream &stream = activeStreams[streamID];
+ updateStream(stream, decoder.decodedHeader());
+ if (continuedFrames[0].flags & FrameFlag::END_STREAM) {
+ finishStream(stream);
+ deleteActiveStream(stream.streamID);
+ }
+ }
+ }
+}
+
+bool QHttp2ProtocolHandler::acceptSetting(Http2::Settings identifier, quint32 newValue)
+{
+ if (identifier == Settings::HEADER_TABLE_SIZE_ID) {
+ if (newValue > maxAcceptableTableSize) {
+ connectionError(PROTOCOL_ERROR, "SETTINGS invalid table size");
+ return false;
+ }
+ encoder.setMaxDynamicTableSize(newValue);
+ }
+
+ if (identifier == Settings::INITIAL_WINDOW_SIZE_ID) {
+ // For every active stream - adjust its window
+ // (and handle possible overflows as errors).
+ if (newValue > quint32(std::numeric_limits<qint32>::max())) {
+ connectionError(FLOW_CONTROL_ERROR, "SETTINGS invalid initial window size");
+ return false;
+ }
+
+ const qint32 delta = qint32(newValue) - streamInitialSendWindowSize;
+ streamInitialSendWindowSize = newValue;
+
+ std::vector<quint32> brokenStreams;
+ brokenStreams.reserve(activeStreams.size());
+ for (auto &stream : activeStreams) {
+ if (sum_will_overflow(stream.sendWindow, delta)) {
+ brokenStreams.push_back(stream.streamID);
+ continue;
+ }
+ stream.sendWindow += delta;
+ }
+
+ for (auto id : brokenStreams) {
+ auto &stream = activeStreams[id];
+ finishStreamWithError(stream, QNetworkReply::ProtocolInvalidOperationError,
+ QLatin1String("SETTINGS window overflow"));
+ sendRST_STREAM(id, PROTOCOL_ERROR);
+ markAsReset(id);
+ deleteActiveStream(id);
+ }
+
+ QMetaObject::invokeMethod(this, "resumeSuspendedStreams", Qt::QueuedConnection);
+ }
+
+ if (identifier == Settings::MAX_CONCURRENT_STREAMS_ID) {
+ if (maxConcurrentStreams > maxPeerConcurrentStreams) {
+ connectionError(PROTOCOL_ERROR, "SETTINGS invalid number of concurrent streams");
+ return false;
+ }
+ maxConcurrentStreams = newValue;
+ }
+
+ if (identifier == Settings::MAX_FRAME_SIZE_ID) {
+ if (newValue < Http2::maxFrameSize || newValue > Http2::maxPayloadSize) {
+ connectionError(PROTOCOL_ERROR, "SETTGINGS max frame size is out of range");
+ return false;
+ }
+ maxFrameSize = newValue;
+ }
+
+ if (identifier == Settings::MAX_HEADER_LIST_SIZE_ID) {
+ // We just remember this value, it can later
+ // prevent us from sending any request (and this
+ // will end up in request/reply error).
+ maxHeaderListSize = newValue;
+ }
+
+ return true;
+}
+
+void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader &headers)
+{
+ const auto httpReply = stream.reply();
+ Q_ASSERT(httpReply);
+ const auto httpReplyPrivate = httpReply->d_func();
+ for (const auto &pair : headers) {
+ const auto &name = pair.name;
+ auto value = pair.value;
+
+ if (name == ":status") {
+ httpReply->setStatusCode(value.left(3).toInt());
+ httpReplyPrivate->reasonPhrase = QString::fromLatin1(value.mid(4));
+ } else if (name == ":version") {
+ httpReplyPrivate->majorVersion = value.at(5) - '0';
+ httpReplyPrivate->minorVersion = value.at(7) - '0';
+ } else if (name == "content-length") {
+ bool ok = false;
+ const qlonglong length = value.toLongLong(&ok);
+ if (ok)
+ httpReply->setContentLength(length);
+ } else {
+ QByteArray binder(", ");
+ if (name == "set-cookie")
+ binder = "\n";
+ httpReply->setHeaderField(name, value.replace('\0', binder));
+ }
+ }
+
+ emit httpReply->headerChanged();
+}
+
+void QHttp2ProtocolHandler::updateStream(Stream &stream, const Http2::FrameReader &frame)
+{
+ Q_ASSERT(frame.type == FrameType::DATA);
+
+ if (const auto length = frame.dataSize()) {
+ const char *data = reinterpret_cast<const char *>(frame.dataBegin());
+ auto &httpRequest = stream.request();
+ auto httpReply = stream.reply();
+ Q_ASSERT(httpReply);
+ auto replyPrivate = httpReply->d_func();
+
+ replyPrivate->compressedData.append(data, length);
+ replyPrivate->totalProgress += length;
+
+ const QByteArray wrapped(data, length);
+ if (httpRequest.d->autoDecompress && replyPrivate->isCompressed()) {
+ QByteDataBuffer inDataBuffer;
+ inDataBuffer.append(wrapped);
+ replyPrivate->uncompressBodyData(&inDataBuffer, &replyPrivate->responseData);
+ } else {
+ replyPrivate->responseData.append(wrapped);
+ }
+
+ if (replyPrivate->shouldEmitSignals()) {
+ emit httpReply->readyRead();
+ emit httpReply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
+ }
+ }
+}
+
+void QHttp2ProtocolHandler::finishStream(Stream &stream)
+{
+ stream.state = Stream::closed;
+ auto httpReply = stream.reply();
+ Q_ASSERT(httpReply);
+ httpReply->disconnect(this);
+ if (stream.data())
+ stream.data()->disconnect(this);
+
+ qCDebug(QT_HTTP2) << "stream" << stream.streamID << "closed";
+
+ emit httpReply->finished();
+}
+
+void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, quint32 errorCode)
+{
+ QNetworkReply::NetworkError error = QNetworkReply::NoError;
+ QString message;
+ qt_error(errorCode, error, message);
+ finishStreamWithError(stream, error, message);
+}
+
+void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, QNetworkReply::NetworkError error,
+ const QString &message)
+{
+ stream.state = Stream::closed;
+ auto httpReply = stream.reply();
+ Q_ASSERT(httpReply);
+ httpReply->disconnect(this);
+ if (stream.data())
+ stream.data()->disconnect(this);
+
+ qCWarning(QT_HTTP2) << "stream" << stream.streamID
+ << "finished with error:" << message;
+
+ // TODO: error message must be translated!!! (tr)
+ emit httpReply->finishedWithError(error, message);
+}
+
+quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message)
+{
+ const qint32 newStreamID = allocateStreamID();
+ if (!newStreamID)
+ return 0;
+
+ Q_ASSERT(!activeStreams.contains(newStreamID));
+
+ const auto reply = message.second;
+ const auto replyPrivate = reply->d_func();
+ replyPrivate->connection = m_connection;
+ replyPrivate->connectionChannel = m_channel;
+ reply->setSpdyWasUsed(true);
+ reply->setProperty("HTTP2StreamID", newStreamID);
+ connect(reply, SIGNAL(destroyed(QObject*)),
+ this, SLOT(_q_replyDestroyed(QObject*)));
+
+ const Stream newStream(message, newStreamID,
+ streamInitialSendWindowSize,
+ streamInitialRecvWindowSize);
+
+ if (auto src = newStream.data()) {
+ connect(src, SIGNAL(readyRead()), this,
+ SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection);
+ src->setProperty("HTTP2StreamID", newStreamID);
+ }
+
+ activeStreams.insert(newStreamID, newStream);
+
+ return newStreamID;
+}
+
+void QHttp2ProtocolHandler::addToSuspended(Stream &stream)
+{
+ qCDebug(QT_HTTP2) << "stream" << stream.streamID
+ << "suspended by flow control";
+ const auto priority = stream.priority();
+ Q_ASSERT(int(priority) >= 0 && int(priority) < 3);
+ suspendedStreams[priority].push_back(stream.streamID);
+}
+
+void QHttp2ProtocolHandler::markAsReset(quint32 streamID)
+{
+ // For now, we trace only client's streams (created by us,
+ // odd integer numbers).
+ if (streamID & 0x1) {
+ qCDebug(QT_HTTP2) << "stream" << streamID << "was reset";
+ // This part is quite tricky: I have to clear this set
+ // so that it does not become tOOO big.
+ if (recycledStreams.size() > maxRecycledStreams) {
+ // At least, I'm erasing the oldest first ...
+ recycledStreams.erase(recycledStreams.begin(),
+ recycledStreams.begin() +
+ recycledStreams.size() / 2);
+ }
+
+ const auto it = std::lower_bound(recycledStreams.begin(), recycledStreams.end(),
+ streamID);
+ if (it != recycledStreams.end() && *it == streamID)
+ return;
+
+ recycledStreams.insert(it, streamID);
+ }
+}
+
+quint32 QHttp2ProtocolHandler::popStreamToResume()
+{
+ quint32 streamID = connectionStreamID;
+ const int nQ = sizeof suspendedStreams / sizeof suspendedStreams[0];
+ using QNR = QHttpNetworkRequest;
+ const QNR::Priority ranks[nQ] = {QNR::HighPriority,
+ QNR::NormalPriority,
+ QNR::LowPriority};
+
+ for (int i = 0; i < nQ; ++i) {
+ auto &queue = suspendedStreams[ranks[i]];
+ auto it = queue.begin();
+ for (; it != queue.end(); ++it) {
+ if (!activeStreams.contains(*it))
+ continue;
+ if (activeStreams[*it].sendWindow > 0)
+ break;
+ }
+
+ if (it != queue.end()) {
+ streamID = *it;
+ queue.erase(it);
+ break;
+ }
+ }
+
+ return streamID;
+}
+
+void QHttp2ProtocolHandler::removeFromSuspended(quint32 streamID)
+{
+ const int nQ = sizeof suspendedStreams / sizeof suspendedStreams[0];
+ for (int i = 0; i < nQ; ++i) {
+ auto &q = suspendedStreams[i];
+ q.erase(std::remove(q.begin(), q.end(), streamID), q.end());
+ }
+}
+
+void QHttp2ProtocolHandler::deleteActiveStream(quint32 streamID)
+{
+ if (activeStreams.contains(streamID)) {
+ auto &stream = activeStreams[streamID];
+ if (stream.reply())
+ stream.reply()->disconnect(this);
+ if (stream.data())
+ stream.data()->disconnect(this);
+ activeStreams.remove(streamID);
+ }
+
+ removeFromSuspended(streamID);
+ if (m_channel->spdyRequestsToSend.size())
+ QMetaObject::invokeMethod(this, "sendRequest", Qt::QueuedConnection);
+}
+
+bool QHttp2ProtocolHandler::streamWasReset(quint32 streamID) const
+{
+ const auto it = std::lower_bound(recycledStreams.begin(),
+ recycledStreams.end(),
+ streamID);
+ return it != recycledStreams.end() && *it == streamID;
+}
+
+void QHttp2ProtocolHandler::resumeSuspendedStreams()
+{
+ while (sessionSendWindowSize > 0) {
+ const auto streamID = popStreamToResume();
+ if (!streamID)
+ return;
+
+ if (!activeStreams.contains(streamID))
+ continue;
+
+ Stream &stream = activeStreams[streamID];
+ if (!sendDATA(stream)) {
+ finishStreamWithError(stream, QNetworkReply::UnknownNetworkError,
+ QLatin1String("failed to send DATA"));
+ sendRST_STREAM(streamID, INTERNAL_ERROR);
+ markAsReset(streamID);
+ deleteActiveStream(streamID);
+ }
+ }
+}
+
+quint32 QHttp2ProtocolHandler::allocateStreamID()
+{
+ // With protocol upgrade streamID == 1 will become
+ // invalid. The logic must be updated.
+ if (nextID > quint32(std::numeric_limits<qint32>::max()))
+ return 0;
+
+ const quint32 streamID = nextID;
+ nextID += 2;
+
+ return streamID;
+}
+
+void QHttp2ProtocolHandler::connectionError(Http2::Http2Error errorCode,
+ const char *message)
+{
+ Q_ASSERT(message);
+ Q_ASSERT(!goingAway);
+
+ qCCritical(QT_HTTP2) << "connection error:" << message;
+
+ goingAway = true;
+ sendGOAWAY(errorCode);
+ const auto error = qt_error(errorCode);
+ m_channel->emitFinishedWithError(error, message);
+
+ for (auto &stream: activeStreams)
+ finishStreamWithError(stream, error, QLatin1String(message));
+
+ closeSession();
+}
+
+void QHttp2ProtocolHandler::closeSession()
+{
+ activeStreams.clear();
+ for (auto &q: suspendedStreams)
+ q.clear();
+ recycledStreams.clear();
+
+ m_channel->close();
+}
+
+#endif // !defined(QT_NO_HTTP) && !defined(QT_NO_SSL)
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qhttp2protocolhandler_p.h b/src/network/access/qhttp2protocolhandler_p.h
new file mode 100644
index 0000000000..b9658982c0
--- /dev/null
+++ b/src/network/access/qhttp2protocolhandler_p.h
@@ -0,0 +1,196 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTP2PROTOCOLHANDLER_P_H
+#define QHTTP2PROTOCOLHANDLER_P_H
+
+#include <private/qhttpnetworkconnectionchannel_p.h>
+#include <private/qabstractprotocolhandler_p.h>
+#include <private/qhttpnetworkrequest_p.h>
+
+#if !defined(QT_NO_HTTP) && !defined(QT_NO_SSL)
+
+#include "http2/http2protocol_p.h"
+#include "http2/http2streams_p.h"
+#include "http2/http2frames_p.h"
+#include "http2/hpacktable_p.h"
+#include "http2/hpack_p.h"
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qflags.h>
+#include <QtCore/qhash.h>
+
+#include <vector>
+#include <limits>
+#include <deque>
+#include <set>
+
+QT_BEGIN_NAMESPACE
+
+class QHttp2ProtocolHandler : public QObject, public QAbstractProtocolHandler
+{
+ Q_OBJECT
+
+public:
+ QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *channel);
+
+ QHttp2ProtocolHandler(const QHttp2ProtocolHandler &rhs) = delete;
+ QHttp2ProtocolHandler(QHttp2ProtocolHandler &&rhs) = delete;
+
+ QHttp2ProtocolHandler &operator = (const QHttp2ProtocolHandler &rhs) = delete;
+ QHttp2ProtocolHandler &operator = (QHttp2ProtocolHandler &&rhs) = delete;
+
+private slots:
+ void _q_uploadDataReadyRead();
+ void _q_replyDestroyed(QObject* reply);
+
+private:
+ using Stream = Http2::Stream;
+
+ void _q_readyRead() override;
+ void _q_receiveReply() override;
+ Q_INVOKABLE bool sendRequest() override;
+
+ bool sendClientPreface();
+ bool sendSETTINGS_ACK();
+ bool sendHEADERS(Stream &stream);
+ bool sendDATA(Stream &stream);
+ Q_INVOKABLE bool sendWINDOW_UPDATE(quint32 streamID, quint32 delta);
+ bool sendRST_STREAM(quint32 streamID, quint32 errorCoder);
+ bool sendGOAWAY(quint32 errorCode);
+
+ void handleDATA();
+ void handleHEADERS();
+ void handlePRIORITY();
+ void handleRST_STREAM();
+ void handleSETTINGS();
+ void handlePUSH_PROMISE();
+ void handlePING();
+ void handleGOAWAY();
+ void handleWINDOW_UPDATE();
+ void handleCONTINUATION();
+
+ void handleContinuedHEADERS();
+
+ bool acceptSetting(Http2::Settings identifier, quint32 newValue);
+
+ void updateStream(Stream &stream, const HPack::HttpHeader &headers);
+ void updateStream(Stream &stream, const Http2::FrameReader &dataFrame);
+ void finishStream(Stream &stream);
+ // Error code send by a peer (GOAWAY/RST_STREAM):
+ void finishStreamWithError(Stream &stream, quint32 errorCode);
+ // Locally encountered error:
+ void finishStreamWithError(Stream &stream, QNetworkReply::NetworkError error,
+ const QString &message);
+
+ // Stream's lifecycle management:
+ quint32 createNewStream(const HttpMessagePair &message);
+ void addToSuspended(Stream &stream);
+ void markAsReset(quint32 streamID);
+ quint32 popStreamToResume();
+ void removeFromSuspended(quint32 streamID);
+ void deleteActiveStream(quint32 streamID);
+ bool streamWasReset(quint32 streamID) const;
+
+ bool prefaceSent = false;
+ // In the current implementation we send
+ // SETTINGS only once, immediately after
+ // the client's preface 24-byte message.
+ bool waitingForSettingsACK = false;
+
+ static const quint32 maxAcceptableTableSize = 16 * HPack::FieldLookupTable::DefaultSize;
+ // HTTP/2 4.3: Header compression is stateful. One compression context and
+ // one decompression context are used for the entire connection.
+ HPack::Decoder decoder;
+ HPack::Encoder encoder;
+
+ QHash<quint32, Stream> activeStreams;
+ std::deque<quint32> suspendedStreams[3]; // 3 for priorities: High, Normal, Low.
+ static const std::deque<quint32>::size_type maxRecycledStreams;
+ std::deque<quint32> recycledStreams;
+
+ // Peer's max frame size.
+ quint32 maxFrameSize = Http2::maxFrameSize;
+
+ Http2::FrameReader inboundFrame;
+ Http2::FrameWriter outboundFrame;
+ // Temporary storage to assemble HEADERS' block
+ // from several CONTINUATION frames ...
+ bool continuationExpected = false;
+ std::vector<Http2::FrameReader> continuedFrames;
+
+ // Peer's max number of streams ...
+ quint32 maxConcurrentStreams = Http2::maxConcurrentStreams;
+
+ // Control flow:
+ static const qint32 sessionMaxRecvWindowSize = Http2::defaultSessionWindowSize * 10;
+ // Signed integer, it can become negative (it's still a valid window size):
+ qint32 sessionRecvWindowSize = sessionMaxRecvWindowSize;
+
+ // We do not negotiate this window size
+ // We have to send WINDOW_UPDATE frames to our peer also.
+ static const qint32 streamInitialRecvWindowSize = Http2::defaultSessionWindowSize;
+
+ // Updated by SETTINGS and WINDOW_UPDATE.
+ qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize;
+ qint32 streamInitialSendWindowSize = Http2::defaultSessionWindowSize;
+
+ // It's unlimited by default, but can be changed via SETTINGS.
+ quint32 maxHeaderListSize = (std::numeric_limits<quint32>::max)();
+
+ Q_INVOKABLE void resumeSuspendedStreams();
+ // Our stream IDs (all odd), the first valid will be 1.
+ quint32 nextID = 1;
+ quint32 allocateStreamID();
+ bool validPeerStreamID() const;
+ bool goingAway = false;
+
+ // Errors:
+ void connectionError(Http2::Http2Error errorCode,
+ const char *message);
+ void closeSession();
+};
+
+QT_END_NAMESPACE
+
+#endif // !defined(QT_NO_HTTP) && !defined(QT_NO_SSL)
+
+#endif
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 79f418f675..e62d662b52 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -82,7 +82,8 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &host
networkLayerState(Unknown),
hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true)
#ifndef QT_NO_SSL
-, channelCount((type == QHttpNetworkConnection::ConnectionTypeSPDY) ? 1 : defaultHttpChannelCount)
+, channelCount((type == QHttpNetworkConnection::ConnectionTypeSPDY || type == QHttpNetworkConnection::ConnectionTypeHTTP2)
+ ? 1 : defaultHttpChannelCount)
#else
, channelCount(defaultHttpChannelCount)
#endif // QT_NO_SSL
@@ -1011,7 +1012,8 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
}
break;
}
- case QHttpNetworkConnection::ConnectionTypeSPDY: {
+ case QHttpNetworkConnection::ConnectionTypeSPDY:
+ case QHttpNetworkConnection::ConnectionTypeHTTP2: {
#ifndef QT_NO_SSL
if (channels[0].spdyRequestsToSend.isEmpty())
return;
diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h
index fefd5af281..4d1bdb74c4 100644
--- a/src/network/access/qhttpnetworkconnection_p.h
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -99,7 +99,8 @@ public:
enum ConnectionType {
ConnectionTypeHTTP,
- ConnectionTypeSPDY
+ ConnectionTypeSPDY,
+ ConnectionTypeHTTP2
};
#ifndef QT_NO_BEARERMANAGEMENT
@@ -161,6 +162,7 @@ private:
friend class QHttpNetworkReply;
friend class QHttpNetworkReplyPrivate;
friend class QHttpNetworkConnectionChannel;
+ friend class QHttp2ProtocolHandler;
friend class QHttpProtocolHandler;
friend class QSpdyProtocolHandler;
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 2f7be01078..791a7495c0 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -47,6 +47,7 @@
#ifndef QT_NO_HTTP
+#include <private/qhttp2protocolhandler_p.h>
#include <private/qhttpprotocolhandler_p.h>
#include <private/qspdyprotocolhandler_p.h>
@@ -971,7 +972,8 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
} while (!connection->d_func()->highPriorityQueue.isEmpty()
|| !connection->d_func()->lowPriorityQueue.isEmpty());
#ifndef QT_NO_SSL
- if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY) {
+ if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY ||
+ connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) {
QList<HttpMessagePair> spdyPairs = spdyRequestsToSend.values();
for (int a = 0; a < spdyPairs.count(); ++a) {
// emit error for all replies
@@ -1003,7 +1005,8 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth)
{
#ifndef QT_NO_SSL
- if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY) {
+ if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY ||
+ connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) {
connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
} else { // HTTP
#endif // QT_NO_SSL
@@ -1043,6 +1046,10 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
// no need to re-queue requests, if SPDY was enabled on the request it
// has gone to the SPDY queue already
break;
+ } else if (nextProtocol == QSslConfiguration::ALPNProtocolHTTP2) {
+ protocolHandler.reset(new QHttp2ProtocolHandler(this));
+ connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP2);
+ break;
} else {
emitFinishedWithError(QNetworkReply::SslHandshakeFailedError,
"detected unknown Next Protocol Negotiation protocol");
@@ -1066,7 +1073,8 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
state = QHttpNetworkConnectionChannel::IdleState;
pendingEncrypt = false;
- if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY) {
+ if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY ||
+ connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) {
// 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)
diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h
index dea13834ed..f3b007f594 100644
--- a/src/network/access/qhttpnetworkreply_p.h
+++ b/src/network/access/qhttpnetworkreply_p.h
@@ -180,6 +180,7 @@ private:
friend class QHttpNetworkConnection;
friend class QHttpNetworkConnectionPrivate;
friend class QHttpNetworkConnectionChannel;
+ friend class QHttp2ProtocolHandler;
friend class QHttpProtocolHandler;
friend class QSpdyProtocolHandler;
};
diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp
index 5d51e7fb1a..802043d847 100644
--- a/src/network/access/qhttpnetworkrequest.cpp
+++ b/src/network/access/qhttpnetworkrequest.cpp
@@ -47,7 +47,7 @@ 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), spdyAllowed(false),
+ autoDecompress(false), pipeliningAllowed(false), spdyAllowed(false), http2Allowed(false),
withCredentials(true), preConnect(false), followRedirect(false), redirectCount(0)
{
}
@@ -61,6 +61,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest
autoDecompress(other.autoDecompress),
pipeliningAllowed(other.pipeliningAllowed),
spdyAllowed(other.spdyAllowed),
+ http2Allowed(other.http2Allowed),
withCredentials(other.withCredentials),
ssl(other.ssl),
preConnect(other.preConnect),
@@ -82,6 +83,7 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot
&& (autoDecompress == other.autoDecompress)
&& (pipeliningAllowed == other.pipeliningAllowed)
&& (spdyAllowed == other.spdyAllowed)
+ && (http2Allowed == other.http2Allowed)
// we do not clear the customVerb in setOperation
&& (operation != QHttpNetworkRequest::Custom || (customVerb == other.customVerb))
&& (withCredentials == other.withCredentials)
@@ -331,6 +333,16 @@ void QHttpNetworkRequest::setSPDYAllowed(bool b)
d->spdyAllowed = b;
}
+bool QHttpNetworkRequest::isHTTP2Allowed() const
+{
+ return d->http2Allowed;
+}
+
+void QHttpNetworkRequest::setHTTP2Allowed(bool b)
+{
+ d->http2Allowed = 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 d85f8ce58f..d1abb76e28 100644
--- a/src/network/access/qhttpnetworkrequest_p.h
+++ b/src/network/access/qhttpnetworkrequest_p.h
@@ -117,6 +117,9 @@ public:
bool isSPDYAllowed() const;
void setSPDYAllowed(bool b);
+ bool isHTTP2Allowed() const;
+ void setHTTP2Allowed(bool b);
+
bool withCredentials() const;
void setWithCredentials(bool b);
@@ -144,6 +147,7 @@ private:
friend class QHttpNetworkConnectionPrivate;
friend class QHttpNetworkConnectionChannel;
friend class QHttpProtocolHandler;
+ friend class QHttp2ProtocolHandler;
friend class QSpdyProtocolHandler;
};
@@ -165,6 +169,7 @@ public:
bool autoDecompress;
bool pipeliningAllowed;
bool spdyAllowed;
+ bool http2Allowed;
bool withCredentials;
bool ssl;
bool preConnect;
diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp
index 3adf71ccfb..e16519c2f2 100644
--- a/src/network/access/qhttpthreaddelegate.cpp
+++ b/src/network/access/qhttpthreaddelegate.cpp
@@ -287,7 +287,13 @@ void QHttpThreadDelegate::startRequest()
QHttpNetworkConnection::ConnectionType connectionType
= QHttpNetworkConnection::ConnectionTypeHTTP;
#ifndef QT_NO_SSL
- if (httpRequest.isSPDYAllowed() && ssl) {
+ if (httpRequest.isHTTP2Allowed() && ssl) {
+ connectionType = QHttpNetworkConnection::ConnectionTypeHTTP2;
+ QList<QByteArray> protocols;
+ protocols << QSslConfiguration::ALPNProtocolHTTP2
+ << QSslConfiguration::NextProtocolHttp1_1;
+ incomingSslConfiguration.setAllowedNextProtocols(protocols);
+ } else if (httpRequest.isSPDYAllowed() && ssl) {
connectionType = QHttpNetworkConnection::ConnectionTypeSPDY;
urlCopy.setScheme(QStringLiteral("spdy")); // to differentiate SPDY requests from HTTPS requests
QList<QByteArray> nextProtocols;
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index d98a1a1d62..b5e44fa29a 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -738,6 +738,9 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
if (request.attribute(QNetworkRequest::SpdyAllowedAttribute).toBool())
httpRequest.setSPDYAllowed(true);
+ if (request.attribute(QNetworkRequest::HTTP2AllowedAttribute).toBool())
+ httpRequest.setHTTP2Allowed(true);
+
if (static_cast<QNetworkRequest::LoadControl>
(newHttpRequest.attribute(QNetworkRequest::AuthenticationReuseAttribute,
QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h
index f284c11cd4..00375f285c 100644
--- a/src/network/access/qnetworkrequest.h
+++ b/src/network/access/qnetworkrequest.h
@@ -87,6 +87,8 @@ public:
BackgroundRequestAttribute,
SpdyAllowedAttribute,
SpdyWasUsedAttribute,
+ HTTP2AllowedAttribute,
+ HTTP2WasUsedAttribute,
EmitAllUploadProgressSignalsAttribute,
FollowRedirectsAttribute,
diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp
index bc8394afdc..75a880f115 100644
--- a/src/network/ssl/qsslconfiguration.cpp
+++ b/src/network/ssl/qsslconfiguration.cpp
@@ -53,6 +53,7 @@ const QSsl::SslOptions QSslConfigurationPrivate::defaultSslOptions = QSsl::SslOp
|QSsl::SslOptionDisableCompression
|QSsl::SslOptionDisableSessionPersistence;
+const char QSslConfiguration::ALPNProtocolHTTP2[] = "h2";
const char QSslConfiguration::NextProtocolSpdy3_0[] = "spdy/3";
const char QSslConfiguration::NextProtocolHttp1_1[] = "http/1.1";
diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h
index b637c4f9c7..1c57bebd65 100644
--- a/src/network/ssl/qsslconfiguration.h
+++ b/src/network/ssl/qsslconfiguration.h
@@ -168,6 +168,7 @@ public:
QByteArray nextNegotiatedProtocol() const;
NextProtocolNegotiationStatus nextProtocolNegotiationStatus() const;
+ static const char ALPNProtocolHTTP2[];
static const char NextProtocolSpdy3_0[];
static const char NextProtocolHttp1_1[];