summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/access')
-rw-r--r--src/network/access/access.pri9
-rw-r--r--src/network/access/http2/bitstreams.cpp336
-rw-r--r--src/network/access/http2/bitstreams_p.h185
-rw-r--r--src/network/access/http2/hpack.cpp551
-rw-r--r--src/network/access/http2/hpack_p.h155
-rw-r--r--src/network/access/http2/hpacktable.cpp533
-rw-r--r--src/network/access/http2/hpacktable_p.h237
-rw-r--r--src/network/access/http2/http2.pri17
-rw-r--r--src/network/access/http2/http2frames.cpp525
-rw-r--r--src/network/access/http2/http2frames_p.h184
-rw-r--r--src/network/access/http2/http2protocol.cpp156
-rw-r--r--src/network/access/http2/http2protocol_p.h175
-rw-r--r--src/network/access/http2/http2streams.cpp100
-rw-r--r--src/network/access/http2/http2streams_p.h103
-rw-r--r--src/network/access/http2/huffman.cpp573
-rw-r--r--src/network/access/http2/huffman_p.h182
-rw-r--r--src/network/access/qabstractnetworkcache.h1
-rw-r--r--src/network/access/qabstractnetworkcache_p.h1
-rw-r--r--src/network/access/qabstractprotocolhandler_p.h4
-rw-r--r--src/network/access/qftp_p.h7
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp1217
-rw-r--r--src/network/access/qhttp2protocolhandler_p.h207
-rw-r--r--src/network/access/qhttpmultipart.h1
-rw-r--r--src/network/access/qhttpmultipart_p.h1
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp7
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h21
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp15
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel_p.h2
-rw-r--r--src/network/access/qhttpnetworkheader_p.h3
-rw-r--r--src/network/access/qhttpnetworkreply.cpp5
-rw-r--r--src/network/access/qhttpnetworkreply_p.h7
-rw-r--r--src/network/access/qhttpnetworkrequest.cpp14
-rw-r--r--src/network/access/qhttpnetworkrequest_p.h7
-rw-r--r--src/network/access/qhttpprotocolhandler_p.h1
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp8
-rw-r--r--src/network/access/qhttpthreaddelegate_p.h1
-rw-r--r--src/network/access/qnetworkaccessauthenticationmanager_p.h1
-rw-r--r--src/network/access/qnetworkaccessbackend_p.h1
-rw-r--r--src/network/access/qnetworkaccesscache_p.h1
-rw-r--r--src/network/access/qnetworkaccesscachebackend_p.h1
-rw-r--r--src/network/access/qnetworkaccessdebugpipebackend_p.h1
-rw-r--r--src/network/access/qnetworkaccessfilebackend_p.h1
-rw-r--r--src/network/access/qnetworkaccessftpbackend_p.h1
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp40
-rw-r--r--src/network/access/qnetworkaccessmanager.h2
-rw-r--r--src/network/access/qnetworkaccessmanager_p.h8
-rw-r--r--src/network/access/qnetworkcookie.cpp2
-rw-r--r--src/network/access/qnetworkcookie.h1
-rw-r--r--src/network/access/qnetworkcookie_p.h1
-rw-r--r--src/network/access/qnetworkcookiejar.h1
-rw-r--r--src/network/access/qnetworkcookiejar_p.h1
-rw-r--r--src/network/access/qnetworkdiskcache.cpp2
-rw-r--r--src/network/access/qnetworkdiskcache.h1
-rw-r--r--src/network/access/qnetworkdiskcache_p.h1
-rw-r--r--src/network/access/qnetworkfile.cpp92
-rw-r--r--src/network/access/qnetworkfile_p.h80
-rw-r--r--src/network/access/qnetworkreply.h1
-rw-r--r--src/network/access/qnetworkreply_p.h1
-rw-r--r--src/network/access/qnetworkreplydataimpl_p.h1
-rw-r--r--src/network/access/qnetworkreplyfileimpl.cpp150
-rw-r--r--src/network/access/qnetworkreplyfileimpl_p.h13
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp48
-rw-r--r--src/network/access/qnetworkreplyhttpimpl_p.h1
-rw-r--r--src/network/access/qnetworkreplyimpl_p.h1
-rw-r--r--src/network/access/qnetworkrequest.h3
-rw-r--r--src/network/access/qnetworkrequest_p.h1
-rw-r--r--src/network/access/qspdyprotocolhandler.cpp8
-rw-r--r--src/network/access/qspdyprotocolhandler_p.h1
68 files changed, 5882 insertions, 137 deletions
diff --git a/src/network/access/access.pri b/src/network/access/access.pri
index 38e9c25269..70ace3f55e 100644
--- a/src/network/access/access.pri
+++ b/src/network/access/access.pri
@@ -37,7 +37,9 @@ HEADERS += \
access/qnetworkdiskcache.h \
access/qhttpthreaddelegate_p.h \
access/qhttpmultipart.h \
- access/qhttpmultipart_p.h
+ access/qhttpmultipart_p.h \
+ access/qnetworkfile_p.h \
+ access/qhttp2protocolhandler_p.h
SOURCES += \
access/qftp.cpp \
@@ -68,8 +70,11 @@ SOURCES += \
access/qabstractnetworkcache.cpp \
access/qnetworkdiskcache.cpp \
access/qhttpthreaddelegate.cpp \
- access/qhttpmultipart.cpp
+ access/qhttpmultipart.cpp \
+ access/qnetworkfile.cpp \
+ access/qhttp2protocolhandler.cpp
mac: LIBS_PRIVATE += -framework Security
include($$PWD/../../3rdparty/zlib_dependency.pri)
+include($$PWD/http2/http2.pri)
diff --git a/src/network/access/http2/bitstreams.cpp b/src/network/access/http2/bitstreams.cpp
new file mode 100644
index 0000000000..d22c7cd4ec
--- /dev/null
+++ b/src/network/access/http2/bitstreams.cpp
@@ -0,0 +1,336 @@
+/****************************************************************************
+**
+** 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 "bitstreams_p.h"
+#include "huffman_p.h"
+
+#include <QtCore/qbytearray.h>
+
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+static_assert(std::numeric_limits<uchar>::digits == 8, "octets expected");
+
+namespace HPack
+{
+
+BitOStream::BitOStream(std::vector<uchar> &b)
+ : buffer(b),
+ // All data 'packed' before:
+ bitsSet(8 * quint64(b.size()))
+{
+}
+
+void BitOStream::writeBits(uchar bits, quint8 bitLength)
+{
+ Q_ASSERT(bitLength <= 8);
+
+ quint8 count = bitsSet % 8; // bits used in buffer.back(), but 0 means 8
+ bits <<= 8 - bitLength; // at top of byte, lower bits clear
+ if (count) { // we have a part-used byte; fill it some more:
+ buffer.back() |= bits >> count;
+ count = 8 - count;
+ } // count bits have been consumed (and 0 now means 0)
+ if (bitLength > count)
+ buffer.push_back(bits << count);
+
+ bitsSet += bitLength;
+}
+
+void BitOStream::write(quint32 src)
+{
+ const quint8 prefixLen = 8 - bitsSet % 8;
+ const quint32 fullPrefix = (1 << prefixLen) - 1;
+
+ // https://http2.github.io/http2-spec/compression.html#low-level.representation,
+ // 5.1
+ if (src < fullPrefix) {
+ writeBits(uchar(src), prefixLen);
+ } else {
+ writeBits(uchar(fullPrefix), prefixLen);
+ // We're on the byte boundary now,
+ // so we can just 'push_back'.
+ Q_ASSERT(!(bitsSet % 8));
+ src -= fullPrefix;
+ while (src >= 128) {
+ buffer.push_back(uchar(src % 128 + 128));
+ src /= 128;
+ bitsSet += 8;
+ }
+ buffer.push_back(src);
+ bitsSet += 8;
+ }
+}
+
+void BitOStream::write(const QByteArray &src, bool compressed)
+{
+ quint32 byteLen = src.size();
+ if (compressed && byteLen) {
+ const auto bitLen = huffman_encoded_bit_length(src);
+ Q_ASSERT(bitLen && std::numeric_limits<quint32>::max() >= (bitLen + 7) / 8);
+ byteLen = (bitLen + 7) / 8;
+ writeBits(uchar(1), 1); // bit set - compressed
+ } else {
+ writeBits(uchar(0), 1); // no compression.
+ }
+
+ write(byteLen);
+
+ if (compressed) {
+ huffman_encode_string(src, *this);
+ } else {
+ bitsSet += quint64(src.size()) * 8;
+ buffer.insert(buffer.end(), src.begin(), src.end());
+ }
+}
+
+quint64 BitOStream::bitLength() const
+{
+ return bitsSet;
+}
+
+quint64 BitOStream::byteLength() const
+{
+ return buffer.size();
+}
+
+const uchar *BitOStream::begin() const
+{
+ return &buffer[0];
+}
+
+const uchar *BitOStream::end() const
+{
+ return &buffer[0] + buffer.size();
+}
+
+void BitOStream::clear()
+{
+ buffer.clear();
+ bitsSet = 0;
+}
+
+BitIStream::BitIStream()
+ : first(),
+ last(),
+ offset(),
+ streamError(Error::NoError)
+{
+}
+
+BitIStream::BitIStream(const uchar *begin, const uchar *end)
+ : first(begin),
+ last(end),
+ offset(),
+ streamError(Error::NoError)
+{
+}
+
+quint64 BitIStream::bitLength() const
+{
+ return quint64(last - first) * 8;
+}
+
+bool BitIStream::hasMoreBits() const
+{
+ return offset < bitLength();
+}
+
+bool BitIStream::skipBits(quint64 nBits)
+{
+ if (nBits > bitLength() || bitLength() - nBits < offset)
+ return false;
+
+ offset += nBits;
+ return true;
+}
+
+bool BitIStream::rewindOffset(quint64 nBits)
+{
+ if (nBits > offset)
+ return false;
+
+ offset -= nBits;
+ return true;
+}
+
+bool BitIStream::read(quint32 *dstPtr)
+{
+ Q_ASSERT(dstPtr);
+ quint32 &dst = *dstPtr;
+
+ // 5.1 Integer Representation
+ //
+ // Integers are used to represent name indexes, header field indexes, or string lengths.
+ // An integer representation can start anywhere within an octet.
+ // To allow for optimized processing, an integer representation always finishes at the end of an octet.
+ // An integer is represented in two parts: a prefix that fills the current octet and an optional
+ // list of octets that are used if the integer value does not fit within the prefix.
+ // The number of bits of the prefix (called N) is a parameter of the integer representation.
+ // If the integer value is small enough, i.e., strictly less than 2N-1, it is compressed within the N-bit prefix.
+ // ...
+ // The prefix size, N, is always between 1 and 8 bits. An integer
+ // starting at an octet boundary will have an 8-bit prefix.
+
+ // Technically, such integers can be of any size, but as we do not have arbitrary-long integers,
+ // everything that does not fit into 'dst' we consider as an error (after all, try to allocate a string
+ // of such size and ... hehehe - send it as a part of a header!
+
+ // This function updates the offset _only_ if the read was successful.
+ if (offset >= bitLength()) {
+ setError(Error::NotEnoughData);
+ return false;
+ }
+
+ setError(Error::NoError);
+
+ const quint32 prefixLen = 8 - offset % 8;
+ const quint32 fullPrefix = (1 << prefixLen) - 1;
+
+ const uchar prefix = uchar(first[offset / 8] & fullPrefix);
+ if (prefix < fullPrefix) {
+ // The number fitted into the prefix bits.
+ dst = prefix;
+ offset += prefixLen;
+ return true;
+ }
+
+ quint32 newOffset = offset + prefixLen;
+ // We have a list of bytes representing an integer ...
+ quint64 val = prefix;
+ quint32 octetPower = 0;
+
+ while (true) {
+ if (newOffset >= bitLength()) {
+ setError(Error::NotEnoughData);
+ return false;
+ }
+
+ const uchar octet = first[newOffset / 8];
+
+ if (octetPower == 28 && octet > 15) {
+ qCritical("integer is too big");
+ setError(Error::InvalidInteger);
+ return false;
+ }
+
+ val += quint32(octet & 0x7f) << octetPower;
+ newOffset += 8;
+
+ if (!(octet & 0x80)) {
+ // The most significant bit of each octet is used
+ // as a continuation flag: its value is set to 1
+ // except for the last octet in the list.
+ break;
+ }
+
+ octetPower += 7;
+ }
+
+ dst = val;
+ offset = newOffset;
+ Q_ASSERT(!(offset % 8));
+
+ return true;
+}
+
+bool BitIStream::read(QByteArray *dstPtr)
+{
+ Q_ASSERT(dstPtr);
+ QByteArray &dst = *dstPtr;
+ //5.2 String Literal Representation
+ //
+ // Header field names and header field values can be represented as string literals.
+ // A string literal is compressed as a sequence of octets, either by directly encoding
+ // the string literal's octets or by using a Huffman code.
+
+ // We update the offset _only_ if the read was successful.
+
+ const quint64 oldOffset = offset;
+ uchar compressed = 0;
+ if (peekBits(offset, 1, &compressed) != 1 || !skipBits(1)) {
+ setError(Error::NotEnoughData);
+ return false;
+ }
+
+ setError(Error::NoError);
+
+ quint32 len = 0;
+ if (read(&len)) {
+ Q_ASSERT(!(offset % 8));
+ if (len <= (bitLength() - offset) / 8) { // We have enough data to read a string ...
+ if (!compressed) {
+ // Now good news, integer always ends on a byte boundary.
+ // We can read 'len' bytes without any bit magic.
+ const char *src = reinterpret_cast<const char *>(first + offset / 8);
+ dst = QByteArray(src, len);
+ offset += quint64(len) * 8;
+ return true;
+ }
+
+ BitIStream slice(first + offset / 8, first + offset / 8 + len);
+ if (huffman_decode_string(slice, &dst)) {
+ offset += quint64(len) * 8;
+ return true;
+ }
+
+ setError(Error::CompressionError);
+ } else {
+ setError(Error::NotEnoughData);
+ }
+ } // else the exact reason was set by read(quint32).
+
+ offset = oldOffset;
+ return false;
+}
+
+BitIStream::Error BitIStream::error() const
+{
+ return streamError;
+}
+
+void BitIStream::setError(Error newState)
+{
+ streamError = newState;
+}
+
+} // namespace HPack
+
+QT_END_NAMESPACE
diff --git a/src/network/access/http2/bitstreams_p.h b/src/network/access/http2/bitstreams_p.h
new file mode 100644
index 0000000000..9eba319dc2
--- /dev/null
+++ b/src/network/access/http2/bitstreams_p.h
@@ -0,0 +1,185 @@
+/****************************************************************************
+**
+** 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 BITSTREAMS_P_H
+#define BITSTREAMS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qdebug.h>
+
+#include <type_traits>
+#include <algorithm>
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+
+class QByteArray;
+
+namespace HPack
+{
+
+// BitOStream works with an external buffer,
+// for example, HEADERS frame.
+class Q_AUTOTEST_EXPORT BitOStream
+{
+public:
+ BitOStream(std::vector<uchar> &buffer);
+
+ // Write 'bitLength' bits from the least significant
+ // bits in 'bits' to bitstream:
+ void writeBits(uchar bits, quint8 bitLength);
+ // HPACK data format, we support:
+ // * 32-bit integers
+ // * strings
+ void write(quint32 src);
+ void write(const QByteArray &src, bool compressed);
+
+ quint64 bitLength() const;
+ quint64 byteLength() const;
+ const uchar *begin() const;
+ const uchar *end() const;
+
+ void clear();
+
+private:
+ Q_DISABLE_COPY(BitOStream);
+
+ std::vector<uchar> &buffer;
+ quint64 bitsSet;
+};
+
+class Q_AUTOTEST_EXPORT BitIStream
+{
+public:
+ // Error is set by 'read' functions.
+ // 'peek' does not set the error,
+ // since it just peeks some bits
+ // without the notion of wrong/right.
+ // 'read' functions only change 'streamOffset'
+ // on success.
+ enum class Error
+ {
+ NoError,
+ NotEnoughData,
+ CompressionError,
+ InvalidInteger
+ };
+
+ BitIStream();
+ BitIStream(const uchar *f, const uchar *l);
+
+ quint64 bitLength() const;
+ bool hasMoreBits() const;
+
+ // peekBits tries to read 'length' bits from the bitstream into
+ // 'dst' ('length' must be <= sizeof(dst) * 8), packing them
+ // starting from the most significant bit of the most significant
+ // byte. It's a template so that we can use it with different
+ // integer types. Returns the number of bits actually read.
+ // Does not change stream's offset.
+
+ template<class T>
+ quint64 peekBits(quint64 from, quint64 length, T *dstPtr) const
+ {
+ static_assert(std::is_unsigned<T>::value, "peekBits: unsigned integer type expected");
+
+ Q_ASSERT(dstPtr);
+ Q_ASSERT(length <= sizeof(T) * 8);
+
+ if (from >= bitLength() || !length)
+ return 0;
+
+ T &dst = *dstPtr;
+ dst = T();
+ length = std::min(length, bitLength() - from);
+
+ const uchar *srcByte = first + from / 8;
+ auto bitsToRead = length + from % 8;
+
+ while (bitsToRead > 8) {
+ dst = (dst << 8) | *srcByte;
+ bitsToRead -= 8;
+ ++srcByte;
+ }
+
+ dst <<= bitsToRead;
+ dst |= *srcByte >> (8 - bitsToRead);
+ dst <<= sizeof(T) * 8 - length;
+
+ return length;
+ }
+
+ quint64 streamOffset() const
+ {
+ return offset;
+ }
+
+ bool skipBits(quint64 nBits);
+ bool rewindOffset(quint64 nBits);
+
+ bool read(quint32 *dstPtr);
+ bool read(QByteArray *dstPtr);
+
+ Error error() const;
+
+private:
+ void setError(Error newState);
+
+ const uchar *first;
+ const uchar *last;
+ quint64 offset;
+ Error streamError;
+};
+
+} // namespace HPack
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/http2/hpack.cpp b/src/network/access/http2/hpack.cpp
new file mode 100644
index 0000000000..95e6f9051b
--- /dev/null
+++ b/src/network/access/http2/hpack.cpp
@@ -0,0 +1,551 @@
+/****************************************************************************
+**
+** 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 "bitstreams_p.h"
+#include "hpack_p.h"
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qdebug.h>
+
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+namespace HPack
+{
+
+HeaderSize header_size(const HttpHeader &header)
+{
+ HeaderSize size(true, 0);
+ for (const HeaderField &field : header) {
+ HeaderSize delta = entry_size(field);
+ if (!delta.first)
+ return HeaderSize();
+ if (std::numeric_limits<quint32>::max() - size.second < delta.second)
+ return HeaderSize();
+ size.second += delta.second;
+ }
+
+ return size;
+}
+
+struct BitPattern
+{
+ BitPattern()
+ : value(),
+ bitLength()
+ {
+ }
+
+ BitPattern(uchar v, uchar len)
+ : value(v),
+ bitLength(len)
+ {
+ }
+
+ uchar value;
+ uchar bitLength;
+};
+
+bool operator == (const BitPattern &lhs, const BitPattern &rhs)
+{
+ return lhs.bitLength == rhs.bitLength && lhs.value == rhs.value;
+}
+
+namespace
+{
+
+using StreamError = BitIStream::Error;
+
+// There are several bit patterns to distinguish header fields:
+// 1 - indexed
+// 01 - literal with incremented indexing
+// 0000 - literal without indexing
+// 0001 - literal, never indexing
+// 001 - dynamic table size update.
+
+// It's always 1 or 0 actually, but the number of bits to extract
+// from the input stream - differs.
+const BitPattern Indexed(1, 1);
+const BitPattern LiteralIncrementalIndexing(1, 2);
+const BitPattern LiteralNoIndexing(0, 4);
+const BitPattern LiteralNeverIndexing(1, 4);
+const BitPattern SizeUpdate(1, 3);
+
+bool is_literal_field(const BitPattern &pattern)
+{
+ return pattern == LiteralIncrementalIndexing
+ || pattern == LiteralNoIndexing
+ || pattern == LiteralNeverIndexing;
+}
+
+void write_bit_pattern(const BitPattern &pattern, BitOStream &outputStream)
+{
+ outputStream.writeBits(pattern.value, pattern.bitLength);
+}
+
+bool read_bit_pattern(const BitPattern &pattern, BitIStream &inputStream)
+{
+ uchar chunk = 0;
+
+ const quint32 bitsRead = inputStream.peekBits(inputStream.streamOffset(),
+ pattern.bitLength, &chunk);
+ if (bitsRead != pattern.bitLength)
+ return false;
+
+ // Since peekBits packs in the most significant bits, shift it!
+ chunk >>= (8 - bitsRead);
+ if (chunk != pattern.value)
+ return false;
+
+ inputStream.skipBits(pattern.bitLength);
+
+ return true;
+}
+
+bool is_request_pseudo_header(const QByteArray &name)
+{
+ return name == ":method" || name == ":scheme" ||
+ name == ":authority" || name == ":path";
+}
+
+} // unnamed namespace
+
+Encoder::Encoder(quint32 size, bool compress)
+ : lookupTable(size, true /*encoder needs search index*/),
+ compressStrings(compress)
+{
+}
+
+quint32 Encoder::dynamicTableSize() const
+{
+ return lookupTable.dynamicDataSize();
+}
+
+bool Encoder::encodeRequest(BitOStream &outputStream, const HttpHeader &header)
+{
+ if (!header.size()) {
+ qDebug("empty header");
+ return false;
+ }
+
+ if (!encodeRequestPseudoHeaders(outputStream, header))
+ return false;
+
+ for (const auto &field : header) {
+ if (is_request_pseudo_header(field.name))
+ continue;
+
+ if (!encodeHeaderField(outputStream, field))
+ return false;
+ }
+
+ return true;
+}
+
+bool Encoder::encodeResponse(BitOStream &outputStream, const HttpHeader &header)
+{
+ if (!header.size()) {
+ qDebug("empty header");
+ return false;
+ }
+
+ if (!encodeResponsePseudoHeaders(outputStream, header))
+ return false;
+
+ for (const auto &field : header) {
+ if (field.name == ":status")
+ continue;
+
+ if (!encodeHeaderField(outputStream, field))
+ return false;
+ }
+
+ return true;
+}
+
+bool Encoder::encodeSizeUpdate(BitOStream &outputStream, quint32 newSize)
+{
+ if (!lookupTable.updateDynamicTableSize(newSize)) {
+ qDebug("failed to update own table size");
+ return false;
+ }
+
+ write_bit_pattern(SizeUpdate, outputStream);
+ outputStream.write(newSize);
+
+ return true;
+}
+
+void Encoder::setMaxDynamicTableSize(quint32 size)
+{
+ // Up to a caller (HTTP2 protocol handler)
+ // to validate this size first.
+ lookupTable.setMaxDynamicTableSize(size);
+}
+
+bool Encoder::encodeRequestPseudoHeaders(BitOStream &outputStream,
+ const HttpHeader &header)
+{
+ // The following pseudo-header fields are defined for HTTP/2 requests:
+ // - The :method pseudo-header field includes the HTTP method
+ // - The :scheme pseudo-header field includes the scheme portion of the target URI
+ // - The :authority pseudo-header field includes the authority portion of the target URI
+ // - The :path pseudo-header field includes the path and query parts of the target URI
+
+ // All HTTP/2 requests MUST include exactly one valid value for the :method,
+ // :scheme, and :path pseudo-header fields, unless it is a CONNECT request
+ // (Section 8.3). An HTTP request that omits mandatory pseudo-header fields
+ // is malformed (Section 8.1.2.6).
+
+ using size_type = decltype(header.size());
+
+ bool methodFound = false;
+ const char *headerName[] = {":authority", ":scheme", ":path"};
+ const size_type nHeaders = sizeof headerName / sizeof headerName[0];
+ bool headerFound[nHeaders] = {};
+
+ for (const auto &field : header) {
+ if (field.name == ":status") {
+ qCritical("invalid pseudo-header (:status) in a request");
+ return false;
+ }
+
+ if (field.name == ":method") {
+ if (methodFound) {
+ qCritical("only one :method pseudo-header is allowed");
+ return false;
+ }
+
+ if (!encodeMethod(outputStream, field))
+ return false;
+ methodFound = true;
+ } else if (field.name == "cookie") {
+ // "crumbs" ...
+ } else {
+ for (size_type j = 0; j < nHeaders; ++j) {
+ if (field.name == headerName[j]) {
+ if (headerFound[j]) {
+ qCritical() << "only one" << headerName[j] << "pseudo-header is allowed";
+ return false;
+ }
+ if (!encodeHeaderField(outputStream, field))
+ return false;
+ headerFound[j] = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!methodFound) {
+ qCritical("mandatory :method pseudo-header not found");
+ return false;
+ }
+
+ // 1: don't demand headerFound[0], as :authority isn't mandatory.
+ for (size_type i = 1; i < nHeaders; ++i) {
+ if (!headerFound[i]) {
+ qCritical() << "mandatory" << headerName[i]
+ << "pseudo-header not found";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Encoder::encodeHeaderField(BitOStream &outputStream, const HeaderField &field)
+{
+ // TODO: at the moment we never use LiteralNo/Never Indexing ...
+
+ // Here we try:
+ // 1. indexed
+ // 2. literal indexed with indexed name/literal value
+ // 3. literal indexed with literal name/literal value
+ if (const auto index = lookupTable.indexOf(field.name, field.value))
+ return encodeIndexedField(outputStream, index);
+
+ if (const auto index = lookupTable.indexOf(field.name)) {
+ return encodeLiteralField(outputStream, LiteralIncrementalIndexing,
+ index, field.value, compressStrings);
+ }
+
+ return encodeLiteralField(outputStream, LiteralIncrementalIndexing,
+ field.name, field.value, compressStrings);
+}
+
+bool Encoder::encodeMethod(BitOStream &outputStream, const HeaderField &field)
+{
+ Q_ASSERT(field.name == ":method");
+ quint32 index = lookupTable.indexOf(field.name, field.value);
+ if (index)
+ return encodeIndexedField(outputStream, index);
+
+ index = lookupTable.indexOf(field.name);
+ Q_ASSERT(index); // ":method" is always in the static table ...
+ return encodeLiteralField(outputStream, LiteralIncrementalIndexing,
+ index, field.value, compressStrings);
+}
+
+bool Encoder::encodeResponsePseudoHeaders(BitOStream &outputStream, const HttpHeader &header)
+{
+ bool statusFound = false;
+ for (const auto &field : header) {
+ if (is_request_pseudo_header(field.name)) {
+ qCritical() << "invalid pseudo-header" << field.name << "in http response";
+ return false;
+ }
+
+ if (field.name == ":status") {
+ if (statusFound) {
+ qDebug("only one :status pseudo-header is allowed");
+ return false;
+ }
+ if (!encodeHeaderField(outputStream, field))
+ return false;
+ statusFound = true;
+ } else if (field.name == "cookie") {
+ // "crumbs"..
+ }
+ }
+
+ if (!statusFound)
+ qCritical("mandatory :status pseudo-header not found");
+
+ return statusFound;
+}
+
+bool Encoder::encodeIndexedField(BitOStream &outputStream, quint32 index) const
+{
+ Q_ASSERT(lookupTable.indexIsValid(index));
+
+ write_bit_pattern(Indexed, outputStream);
+ outputStream.write(index);
+
+ return true;
+}
+
+bool Encoder::encodeLiteralField(BitOStream &outputStream, const BitPattern &fieldType,
+ const QByteArray &name, const QByteArray &value,
+ bool withCompression)
+{
+ Q_ASSERT(is_literal_field(fieldType));
+ // According to HPACK, the bit pattern is
+ // 01 | 000000 (integer 0 that fits into 6-bit prefix),
+ // since integers always end on byte boundary,
+ // this also implies that we always start at bit offset == 0.
+ if (outputStream.bitLength() % 8) {
+ qCritical("invalid bit offset");
+ return false;
+ }
+
+ if (fieldType == LiteralIncrementalIndexing) {
+ if (!lookupTable.prependField(name, value))
+ qDebug("failed to prepend a new field");
+ }
+
+ write_bit_pattern(fieldType, outputStream);
+
+ outputStream.write(0);
+ outputStream.write(name, withCompression);
+ outputStream.write(value, withCompression);
+
+ return true;
+}
+
+bool Encoder::encodeLiteralField(BitOStream &outputStream, const BitPattern &fieldType,
+ quint32 nameIndex, const QByteArray &value,
+ bool withCompression)
+{
+ Q_ASSERT(is_literal_field(fieldType));
+
+ QByteArray name;
+ const bool found = lookupTable.fieldName(nameIndex, &name);
+ Q_UNUSED(found) Q_ASSERT(found);
+
+ if (fieldType == LiteralIncrementalIndexing) {
+ if (!lookupTable.prependField(name, value))
+ qDebug("failed to prepend a new field");
+ }
+
+ write_bit_pattern(fieldType, outputStream);
+ outputStream.write(nameIndex);
+ outputStream.write(value, withCompression);
+
+ return true;
+}
+
+Decoder::Decoder(quint32 size)
+ : lookupTable{size, false /* we do not need search index ... */}
+{
+}
+
+bool Decoder::decodeHeaderFields(BitIStream &inputStream)
+{
+ header.clear();
+ while (true) {
+ if (read_bit_pattern(Indexed, inputStream)) {
+ if (!decodeIndexedField(inputStream))
+ return false;
+ } else if (read_bit_pattern(LiteralIncrementalIndexing, inputStream)) {
+ if (!decodeLiteralField(LiteralIncrementalIndexing, inputStream))
+ return false;
+ } else if (read_bit_pattern(LiteralNoIndexing, inputStream)) {
+ if (!decodeLiteralField(LiteralNoIndexing, inputStream))
+ return false;
+ } else if (read_bit_pattern(LiteralNeverIndexing, inputStream)) {
+ if (!decodeLiteralField(LiteralNeverIndexing, inputStream))
+ return false;
+ } else if (read_bit_pattern(SizeUpdate, inputStream)) {
+ if (!decodeSizeUpdate(inputStream))
+ return false;
+ } else {
+ return inputStream.bitLength() == inputStream.streamOffset();
+ }
+ }
+
+ return false;
+}
+
+quint32 Decoder::dynamicTableSize() const
+{
+ return lookupTable.dynamicDataSize();
+}
+
+void Decoder::setMaxDynamicTableSize(quint32 size)
+{
+ // Up to a caller (HTTP2 protocol handler)
+ // to validate this size first.
+ lookupTable.setMaxDynamicTableSize(size);
+}
+
+bool Decoder::decodeIndexedField(BitIStream &inputStream)
+{
+ quint32 index = 0;
+ if (inputStream.read(&index)) {
+ if (!index) {
+ // "The index value of 0 is not used.
+ // It MUST be treated as a decoding
+ // error if found in an indexed header
+ // field representation."
+ return false;
+ }
+
+ QByteArray name, value;
+ if (lookupTable.field(index, &name, &value))
+ return processDecodedField(Indexed, name, value);
+ } else {
+ handleStreamError(inputStream);
+ }
+
+ return false;
+}
+
+bool Decoder::decodeSizeUpdate(BitIStream &inputStream)
+{
+ // For now, just read and skip bits.
+ quint32 maxSize = 0;
+ if (inputStream.read(&maxSize)) {
+ if (!lookupTable.updateDynamicTableSize(maxSize))
+ return false;
+
+ return true;
+ }
+
+ handleStreamError(inputStream);
+ return false;
+}
+
+bool Decoder::decodeLiteralField(const BitPattern &fieldType, BitIStream &inputStream)
+{
+ // https://http2.github.io/http2-spec/compression.html
+ // 6.2.1, 6.2.2, 6.2.3
+ // Format for all 'literal' is similar,
+ // the difference - is how we update/not our lookup table.
+ quint32 index = 0;
+ if (inputStream.read(&index)) {
+ QByteArray name;
+ if (!index) {
+ // Read a string.
+ if (!inputStream.read(&name)) {
+ handleStreamError(inputStream);
+ return false;
+ }
+ } else {
+ if (!lookupTable.fieldName(index, &name))
+ return false;
+ }
+
+ QByteArray value;
+ if (inputStream.read(&value))
+ return processDecodedField(fieldType, name, value);
+ }
+
+ handleStreamError(inputStream);
+
+ return false;
+}
+
+bool Decoder::processDecodedField(const BitPattern &fieldType,
+ const QByteArray &name,
+ const QByteArray &value)
+{
+ if (fieldType == LiteralIncrementalIndexing) {
+ if (!lookupTable.prependField(name, value))
+ return false;
+ }
+
+ header.push_back(HeaderField(name, value));
+ return true;
+}
+
+void Decoder::handleStreamError(BitIStream &inputStream)
+{
+ const auto errorCode(inputStream.error());
+ if (errorCode == StreamError::NoError)
+ return;
+
+ // For now error handling not needed here,
+ // HTTP2 layer will end with session error/COMPRESSION_ERROR.
+}
+
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/http2/hpack_p.h b/src/network/access/http2/hpack_p.h
new file mode 100644
index 0000000000..6a1d30d87b
--- /dev/null
+++ b/src/network/access/http2/hpack_p.h
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** 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 HPACK_P_H
+#define HPACK_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "hpacktable_p.h"
+
+#include <QtCore/qglobal.h>
+
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+
+class QByteArray;
+
+namespace HPack
+{
+
+using HttpHeader = std::vector<HeaderField>;
+HeaderSize header_size(const HttpHeader &header);
+
+class Q_AUTOTEST_EXPORT Encoder
+{
+public:
+ Encoder(quint32 maxTableSize, bool compressStrings);
+
+ quint32 dynamicTableSize() const;
+
+ bool encodeRequest(class BitOStream &outputStream,
+ const HttpHeader &header);
+ bool encodeResponse(BitOStream &outputStream,
+ const HttpHeader &header);
+
+ bool encodeSizeUpdate(BitOStream &outputStream,
+ quint32 newSize);
+
+ void setMaxDynamicTableSize(quint32 size);
+
+private:
+ bool encodeRequestPseudoHeaders(BitOStream &outputStream,
+ const HttpHeader &header);
+ bool encodeHeaderField(BitOStream &outputStream,
+ const HeaderField &field);
+ bool encodeMethod(BitOStream &outputStream,
+ const HeaderField &field);
+
+ bool encodeResponsePseudoHeaders(BitOStream &outputStream,
+ const HttpHeader &header);
+
+ bool encodeIndexedField(BitOStream &outputStream, quint32 index) const;
+
+
+ bool encodeLiteralField(BitOStream &outputStream,
+ const struct BitPattern &fieldType,
+ quint32 nameIndex,
+ const QByteArray &value,
+ bool withCompression);
+
+ bool encodeLiteralField(BitOStream &outputStream,
+ const BitPattern &fieldType,
+ const QByteArray &name,
+ const QByteArray &value,
+ bool withCompression);
+
+ FieldLookupTable lookupTable;
+ bool compressStrings;
+};
+
+class Q_AUTOTEST_EXPORT Decoder
+{
+public:
+ Decoder(quint32 maxTableSize);
+
+ bool decodeHeaderFields(class BitIStream &inputStream);
+
+ const HttpHeader &decodedHeader() const
+ {
+ return header;
+ }
+
+ quint32 dynamicTableSize() const;
+
+ void setMaxDynamicTableSize(quint32 size);
+
+private:
+
+ bool decodeIndexedField(BitIStream &inputStream);
+ bool decodeSizeUpdate(BitIStream &inputStream);
+ bool decodeLiteralField(const BitPattern &fieldType,
+ BitIStream &inputStream);
+
+ bool processDecodedField(const BitPattern &fieldType,
+ const QByteArray &name,
+ const QByteArray &value);
+
+ void handleStreamError(BitIStream &inputStream);
+
+ HttpHeader header;
+ FieldLookupTable lookupTable;
+};
+
+}
+
+QT_END_NAMESPACE
+
+#endif
+
diff --git a/src/network/access/http2/hpacktable.cpp b/src/network/access/http2/hpacktable.cpp
new file mode 100644
index 0000000000..db9574e2bc
--- /dev/null
+++ b/src/network/access/http2/hpacktable.cpp
@@ -0,0 +1,533 @@
+/****************************************************************************
+**
+** 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 "hpacktable_p.h"
+
+#include <QtCore/qdebug.h>
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+
+
+QT_BEGIN_NAMESPACE
+
+namespace HPack
+{
+
+HeaderSize entry_size(const QByteArray &name, const QByteArray &value)
+{
+ // 32 comes from HPACK:
+ // "4.1 Calculating Table Size
+ // Note: The additional 32 octets account for an estimated overhead associated
+ // with an entry. For example, an entry structure using two 64-bit pointers
+ // to reference the name and the value of the entry and two 64-bit integers
+ // for counting the number of references to the name and value would have
+ // 32 octets of overhead."
+
+ const unsigned sum = unsigned(name.size()) + value.size();
+ if (std::numeric_limits<unsigned>::max() - 32 < sum)
+ return HeaderSize();
+ if (sum + 32 > std::numeric_limits<quint32>::max())
+ return HeaderSize();
+ return HeaderSize(true, quint32(sum + 32));
+}
+
+namespace
+{
+
+int compare(const QByteArray &lhs, const QByteArray &rhs)
+{
+ if (const int minLen = std::min(lhs.size(), rhs.size())) {
+ // We use memcmp, since strings in headers are allowed
+ // to contain '\0'.
+ const int cmp = std::memcmp(lhs.constData(), rhs.constData(), minLen);
+ if (cmp)
+ return cmp;
+ }
+
+ return lhs.size() - rhs.size();
+}
+
+} // unnamed namespace
+
+FieldLookupTable::SearchEntry::SearchEntry()
+ : field(),
+ chunk(),
+ offset(),
+ table()
+{
+}
+
+FieldLookupTable::SearchEntry::SearchEntry(const HeaderField *f,
+ const Chunk *c,
+ quint32 o,
+ const FieldLookupTable *t)
+ : field(f),
+ chunk(c),
+ offset(o),
+ table(t)
+{
+ Q_ASSERT(field);
+}
+
+bool FieldLookupTable::SearchEntry::operator < (const SearchEntry &rhs)const
+{
+ Q_ASSERT(field);
+ Q_ASSERT(rhs.field);
+
+ int cmp = compare(field->name, rhs.field->name);
+ if (cmp)
+ return cmp < 0;
+
+ cmp = compare(field->value, rhs.field->value);
+ if (cmp)
+ return cmp < 0;
+
+ if (!chunk) // 'this' is not in the searchIndex.
+ return rhs.chunk;
+
+ if (!rhs.chunk) // not in the searchIndex.
+ return false;
+
+ Q_ASSERT(table);
+ Q_ASSERT(rhs.table == table);
+
+ const quint32 leftChunkIndex = table->indexOfChunk(chunk);
+ const quint32 rightChunkIndex = rhs.table->indexOfChunk(rhs.chunk);
+
+ // Later added - smaller is chunk index (since we push_front).
+ if (leftChunkIndex != rightChunkIndex)
+ return leftChunkIndex > rightChunkIndex;
+
+ // Later added - smaller is offset.
+ return offset > rhs.offset;
+}
+
+// This data is from HPACK's specs and it's quite
+// conveniently sorted == works with binary search as it is.
+// Later this can probably change and instead of simple
+// vector we'll just reuse FieldLookupTable.
+// TODO: it makes sense to generate this table while ...
+// configuring/building Qt (some script downloading/parsing/generating
+// would be quite handy).
+const std::vector<HeaderField> &staticTable()
+{
+ static std::vector<HeaderField> table = {
+ {":authority", ""},
+ {":method", "GET"},
+ {":method", "POST"},
+ {":path", "/"},
+ {":path", "/index.html"},
+ {":scheme", "http"},
+ {":scheme", "https"},
+ {":status", "200"},
+ {":status", "204"},
+ {":status", "206"},
+ {":status", "304"},
+ {":status", "400"},
+ {":status", "404"},
+ {":status", "500"},
+ {"accept-charset", ""},
+ {"accept-encoding", "gzip, deflate"},
+ {"accept-language", ""},
+ {"accept-ranges", ""},
+ {"accept", ""},
+ {"access-control-allow-origin", ""},
+ {"age", ""},
+ {"allow", ""},
+ {"authorization", ""},
+ {"cache-control", ""},
+ {"content-disposition", ""},
+ {"content-encoding", ""},
+ {"content-language", ""},
+ {"content-length", ""},
+ {"content-location", ""},
+ {"content-range", ""},
+ {"content-type", ""},
+ {"cookie", ""},
+ {"date", ""},
+ {"etag", ""},
+ {"expect", ""},
+ {"expires", ""},
+ {"from", ""},
+ {"host", ""},
+ {"if-match", ""},
+ {"if-modified-since", ""},
+ {"if-none-match", ""},
+ {"if-range", ""},
+ {"if-unmodified-since", ""},
+ {"last-modified", ""},
+ {"link", ""},
+ {"location", ""},
+ {"max-forwards", ""},
+ {"proxy-authenticate", ""},
+ {"proxy-authorization", ""},
+ {"range", ""},
+ {"referer", ""},
+ {"refresh", ""},
+ {"retry-after", ""},
+ {"server", ""},
+ {"set-cookie", ""},
+ {"strict-transport-security", ""},
+ {"transfer-encoding", ""},
+ {"user-agent", ""},
+ {"vary", ""},
+ {"via", ""},
+ {"www-authenticate", ""}
+ };
+
+ return table;
+}
+
+FieldLookupTable::FieldLookupTable(quint32 maxSize, bool use)
+ : maxTableSize(maxSize),
+ tableCapacity(maxSize),
+ useIndex(use),
+ nDynamic(),
+ begin(),
+ end(),
+ dataSize()
+{
+}
+
+
+bool FieldLookupTable::prependField(const QByteArray &name, const QByteArray &value)
+{
+ const auto entrySize = entry_size(name, value);
+ if (!entrySize.first)
+ return false;
+
+ if (entrySize.second > tableCapacity) {
+ clearDynamicTable();
+ return true;
+ }
+
+ while (nDynamic && tableCapacity - dataSize < entrySize.second)
+ evictEntry();
+
+ if (!begin) {
+ // Either no more space or empty table ...
+ chunks.push_front(ChunkPtr(new Chunk(ChunkSize)));
+ end += ChunkSize;
+ begin = ChunkSize;
+ }
+
+ --begin;
+
+ dataSize += entrySize.second;
+ ++nDynamic;
+
+ auto &newField = front();
+ newField.name = name;
+ newField.value = value;
+
+ if (useIndex) {
+ const auto result = searchIndex.insert(frontKey());
+ Q_UNUSED(result) Q_ASSERT(result.second);
+ }
+
+ return true;
+}
+
+void FieldLookupTable::evictEntry()
+{
+ if (!nDynamic)
+ return;
+
+ Q_ASSERT(end != begin);
+
+ if (useIndex) {
+ const auto res = searchIndex.erase(backKey());
+ Q_UNUSED(res) Q_ASSERT(res == 1);
+ }
+
+ const HeaderField &field = back();
+ const auto entrySize = entry_size(field);
+ Q_ASSERT(entrySize.first);
+ Q_ASSERT(dataSize >= entrySize.second);
+ dataSize -= entrySize.second;
+
+ --nDynamic;
+ --end;
+
+ if (end == begin) {
+ Q_ASSERT(chunks.size() == 1);
+ end = ChunkSize;
+ begin = end;
+ } else if (!(end % ChunkSize)) {
+ chunks.pop_back();
+ }
+}
+
+quint32 FieldLookupTable::numberOfEntries() const
+{
+ return quint32(staticTable().size()) + nDynamic;
+}
+
+quint32 FieldLookupTable::numberOfStaticEntries() const
+{
+ return quint32(staticTable().size());
+}
+
+quint32 FieldLookupTable::numberOfDynamicEntries() const
+{
+ return nDynamic;
+}
+
+quint32 FieldLookupTable::dynamicDataSize() const
+{
+ return dataSize;
+}
+
+void FieldLookupTable::clearDynamicTable()
+{
+ searchIndex.clear();
+ chunks.clear();
+ begin = 0;
+ end = 0;
+ nDynamic = 0;
+ dataSize = 0;
+}
+
+bool FieldLookupTable::indexIsValid(quint32 index) const
+{
+ return index && index <= staticTable().size() + nDynamic;
+}
+
+quint32 FieldLookupTable::indexOf(const QByteArray &name, const QByteArray &value)const
+{
+ // Start from the static part first:
+ const auto &table = staticTable();
+ const HeaderField field(name, value);
+ const auto staticPos = std::lower_bound(table.begin(), table.end(), field,
+ [](const HeaderField &lhs, const HeaderField &rhs) {
+ int cmp = compare(lhs.name, rhs.name);
+ if (cmp)
+ return cmp < 0;
+ return compare(lhs.value, rhs.value) < 0;
+ });
+ if (staticPos != table.end()) {
+ if (staticPos->name == name && staticPos->value == value)
+ return staticPos - table.begin() + 1;
+ }
+
+ // Now we have to lookup in our dynamic part ...
+ if (!useIndex) {
+ qCritical("lookup in dynamic table requires search index enabled");
+ return 0;
+ }
+
+ const SearchEntry key(&field, nullptr, 0, this);
+ const auto pos = searchIndex.lower_bound(key);
+ if (pos != searchIndex.end()) {
+ const HeaderField &found = *pos->field;
+ if (found.name == name && found.value == value)
+ return keyToIndex(*pos);
+ }
+
+ return 0;
+}
+
+quint32 FieldLookupTable::indexOf(const QByteArray &name) const
+{
+ // Start from the static part first:
+ const auto &table = staticTable();
+ const HeaderField field(name, QByteArray());
+ const auto staticPos = std::lower_bound(table.begin(), table.end(), field,
+ [](const HeaderField &lhs, const HeaderField &rhs) {
+ return compare(lhs.name, rhs.name) < 0;
+ });
+ if (staticPos != table.end()) {
+ if (staticPos->name == name)
+ return staticPos - table.begin() + 1;
+ }
+
+ // Now we have to lookup in our dynamic part ...
+ if (!useIndex) {
+ qCritical("lookup in dynamic table requires search index enabled");
+ return 0;
+ }
+
+ const SearchEntry key(&field, nullptr, 0, this);
+ const auto pos = searchIndex.lower_bound(key);
+ if (pos != searchIndex.end()) {
+ const HeaderField &found = *pos->field;
+ if (found.name == name)
+ return keyToIndex(*pos);
+ }
+
+ return 0;
+}
+
+bool FieldLookupTable::field(quint32 index, QByteArray *name, QByteArray *value) const
+{
+ Q_ASSERT(name);
+ Q_ASSERT(value);
+
+ if (!indexIsValid(index))
+ return false;
+
+ const auto &table = staticTable();
+ if (index - 1 < table.size()) {
+ *name = table[index - 1].name;
+ *value = table[index - 1].value;
+ return true;
+ }
+
+ index = index - 1 - quint32(table.size()) + begin;
+ const auto chunkIndex = index / ChunkSize;
+ Q_ASSERT(chunkIndex < chunks.size());
+ const auto offset = index % ChunkSize;
+ const HeaderField &found = (*chunks[chunkIndex])[offset];
+ *name = found.name;
+ *value = found.value;
+
+ return true;
+}
+
+bool FieldLookupTable::fieldName(quint32 index, QByteArray *dst) const
+{
+ Q_ASSERT(dst);
+ return field(index, dst, &dummyDst);
+}
+
+bool FieldLookupTable::fieldValue(quint32 index, QByteArray *dst) const
+{
+ Q_ASSERT(dst);
+ return field(index, &dummyDst, dst);
+}
+
+const HeaderField &FieldLookupTable::front() const
+{
+ Q_ASSERT(nDynamic && begin != end && chunks.size());
+ return (*chunks[0])[begin];
+}
+
+HeaderField &FieldLookupTable::front()
+{
+ Q_ASSERT(nDynamic && begin != end && chunks.size());
+ return (*chunks[0])[begin];
+}
+
+const HeaderField &FieldLookupTable::back() const
+{
+ Q_ASSERT(nDynamic && end && end != begin);
+
+ const quint32 absIndex = end - 1;
+ const quint32 chunkIndex = absIndex / ChunkSize;
+ Q_ASSERT(chunkIndex < chunks.size());
+ const quint32 offset = absIndex % ChunkSize;
+ return (*chunks[chunkIndex])[offset];
+}
+
+quint32 FieldLookupTable::indexOfChunk(const Chunk *chunk) const
+{
+ Q_ASSERT(chunk);
+
+ for (size_type i = 0; i < chunks.size(); ++i) {
+ if (chunks[i].get() == chunk)
+ return quint32(i);
+ }
+
+ Q_UNREACHABLE();
+ return 0;
+}
+
+quint32 FieldLookupTable::keyToIndex(const SearchEntry &key) const
+{
+ Q_ASSERT(key.chunk);
+
+ const auto chunkIndex = indexOfChunk(key.chunk);
+ const auto offset = key.offset;
+ Q_ASSERT(offset < ChunkSize);
+ Q_ASSERT(chunkIndex || offset >= begin);
+
+ return quint32(offset + chunkIndex * ChunkSize - begin + 1 + staticTable().size());
+}
+
+FieldLookupTable::SearchEntry FieldLookupTable::frontKey() const
+{
+ Q_ASSERT(chunks.size() && end != begin);
+ return SearchEntry(&front(), chunks.front().get(), begin, this);
+}
+
+FieldLookupTable::SearchEntry FieldLookupTable::backKey() const
+{
+ Q_ASSERT(chunks.size() && end != begin);
+
+ const HeaderField &field = back();
+ const quint32 absIndex = end - 1;
+ const auto offset = absIndex % ChunkSize;
+ const auto chunk = chunks[absIndex / ChunkSize].get();
+
+ return SearchEntry(&field, chunk, offset, this);
+}
+
+bool FieldLookupTable::updateDynamicTableSize(quint32 size)
+{
+ if (!size) {
+ clearDynamicTable();
+ return true;
+ }
+
+ if (size > maxTableSize)
+ return false;
+
+ tableCapacity = size;
+ while (nDynamic && dataSize > tableCapacity)
+ evictEntry();
+
+ return true;
+}
+
+void FieldLookupTable::setMaxDynamicTableSize(quint32 size)
+{
+ // This is for an external user, for example, HTTP2 protocol
+ // layer that can receive SETTINGS frame from its peer.
+ // No validity checks here, up to this external user.
+ // We update max size and capacity (this can also result in
+ // items evicted or even dynamic table completely cleared).
+ maxTableSize = size;
+ updateDynamicTableSize(size);
+}
+
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/http2/hpacktable_p.h b/src/network/access/http2/hpacktable_p.h
new file mode 100644
index 0000000000..aaea89b986
--- /dev/null
+++ b/src/network/access/http2/hpacktable_p.h
@@ -0,0 +1,237 @@
+/****************************************************************************
+**
+** 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 HPACKTABLE_P_H
+#define HPACKTABLE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qpair.h>
+
+#include <vector>
+#include <memory>
+#include <deque>
+#include <set>
+
+QT_BEGIN_NAMESPACE
+
+namespace HPack
+{
+
+struct Q_AUTOTEST_EXPORT HeaderField
+{
+ HeaderField()
+ {
+ }
+
+ HeaderField(const QByteArray &n, const QByteArray &v)
+ : name(n),
+ value(v)
+ {
+ }
+
+ bool operator == (const HeaderField &rhs) const
+ {
+ return name == rhs.name && value == rhs.value;
+ }
+
+ QByteArray name;
+ QByteArray value;
+};
+
+using HeaderSize = QPair<bool, quint32>;
+
+HeaderSize entry_size(const QByteArray &name, const QByteArray &value);
+
+inline HeaderSize entry_size(const HeaderField &entry)
+{
+ return entry_size(entry.name, entry.value);
+}
+
+/*
+ Lookup table consists of two parts (HPACK, 2.3):
+ the immutable static table (pre-defined by HPACK's specs)
+ and dynamic table which is updated while
+ compressing/decompressing headers.
+
+ Table must provide/implement:
+ 1. Fast random access - we read fields' indices from
+ HPACK's bit stream.
+ 2. FIFO for dynamic part - to push new items to the front
+ and evict them from the back (HPACK, 2.3.2).
+ 3. Fast lookup - encoder receives pairs of strings
+ (name|value) and it has to find an index for a pair
+ as the whole or for a name at least (if it's already
+ in either static or dynamic table).
+
+ Static table is an immutable vector.
+
+ Dynamic part is implemented in a way similar to std::deque -
+ it's a vector of pointers to chunks. Each chunk is a vector of
+ (name|value) pairs. Once allocated with a fixed size, chunk
+ never re-allocates its data, so entries' addresses do not change.
+ We add new chunks prepending them to the front of a vector,
+ in each chunk we fill (name|value) pairs starting from the back
+ of the chunk (this simplifies item eviction/FIFO).
+ Given a 'linear' index we can find a chunk number and
+ offset in this chunk - random access.
+
+ Lookup in a static part is straightforward:
+ it's an (immutable) vector, data is sorted,
+ contains no duplicates, we use binary search comparing string values.
+
+ To provide a lookup in dynamic table faster than a linear search,
+ we have an std::set of 'SearchEntries', where each entry contains:
+ - a pointer to a (name|value) pair (to compare
+ name|value strings).
+ - a pointer to a chunk containing this pair and
+ - an offset within this chunk - to calculate a
+ 'linear' index.
+
+ Entries in a table can be duplicated (HPACK, 2.3.2),
+ if we evict an entry, we must update our index removing
+ the exactly right key, thus keys in this set are sorted
+ by name|value pairs first, and then by chunk index/offset
+ (so that NewSearchEntryKey < OldSearchEntry even if strings
+ are equal).
+*/
+
+class Q_AUTOTEST_EXPORT FieldLookupTable
+{
+public:
+ enum
+ {
+ ChunkSize = 16,
+ DefaultSize = 4096 // Recommended by HTTP2.
+ };
+
+ FieldLookupTable(quint32 maxTableSize, bool useIndex);
+
+ bool prependField(const QByteArray &name, const QByteArray &value);
+ void evictEntry();
+
+ quint32 numberOfEntries() const;
+ quint32 numberOfStaticEntries() const;
+ quint32 numberOfDynamicEntries() const;
+ quint32 dynamicDataSize() const;
+ void clearDynamicTable();
+
+ bool indexIsValid(quint32 index) const;
+ quint32 indexOf(const QByteArray &name, const QByteArray &value) const;
+ quint32 indexOf(const QByteArray &name) const;
+ bool field(quint32 index, QByteArray *name, QByteArray *value) const;
+ bool fieldName(quint32 index, QByteArray *dst) const;
+ bool fieldValue(quint32 index, QByteArray *dst) const;
+
+ bool updateDynamicTableSize(quint32 size);
+ void setMaxDynamicTableSize(quint32 size);
+
+private:
+ // Table's maximum size is controlled
+ // by SETTINGS_HEADER_TABLE_SIZE (HTTP/2, 6.5.2).
+ quint32 maxTableSize;
+ // The tableCapacity is how many bytes the table
+ // can currently hold. It cannot exceed maxTableSize.
+ // It can be modified by a special message in
+ // the HPACK bit stream (HPACK, 6.3).
+ quint32 tableCapacity;
+
+ using Chunk = std::vector<HeaderField>;
+ using ChunkPtr = std::unique_ptr<Chunk>;
+ std::deque<ChunkPtr> chunks;
+ using size_type = std::deque<ChunkPtr>::size_type;
+
+ struct SearchEntry;
+ friend struct SearchEntry;
+
+ struct SearchEntry
+ {
+ SearchEntry();
+ SearchEntry(const HeaderField *f, const Chunk *c,
+ quint32 o, const FieldLookupTable *t);
+
+ const HeaderField *field;
+ const Chunk *chunk;
+ const quint32 offset;
+ const FieldLookupTable *table;
+
+ bool operator < (const SearchEntry &rhs) const;
+ };
+
+ bool useIndex;
+ std::set<SearchEntry> searchIndex;
+
+ SearchEntry frontKey() const;
+ SearchEntry backKey() const;
+
+ bool fieldAt(quint32 index, HeaderField *field) const;
+
+ const HeaderField &front() const;
+ HeaderField &front();
+ const HeaderField &back() const;
+
+ quint32 nDynamic;
+ quint32 begin;
+ quint32 end;
+ quint32 dataSize;
+
+ quint32 indexOfChunk(const Chunk *chunk) const;
+ quint32 keyToIndex(const SearchEntry &key) const;
+
+ mutable QByteArray dummyDst;
+
+ Q_DISABLE_COPY(FieldLookupTable);
+};
+
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/http2/http2.pri b/src/network/access/http2/http2.pri
new file mode 100644
index 0000000000..e9f30aeb4a
--- /dev/null
+++ b/src/network/access/http2/http2.pri
@@ -0,0 +1,17 @@
+HEADERS += \
+ access/http2/bitstreams_p.h \
+ access/http2/huffman_p.h \
+ access/http2/hpack_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/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..6abed315ca
--- /dev/null
+++ b/src/network/access/http2/http2frames_p.h
@@ -0,0 +1,184 @@
+/****************************************************************************
+**
+** 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
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "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..5c46949e23
--- /dev/null
+++ b/src/network/access/http2/http2protocol_p.h
@@ -0,0 +1,175 @@
+/****************************************************************************
+**
+** 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
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <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..5d9a6ab512
--- /dev/null
+++ b/src/network/access/http2/http2streams_p.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** 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
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/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/http2/huffman.cpp b/src/network/access/http2/huffman.cpp
new file mode 100644
index 0000000000..0c1aa54dd6
--- /dev/null
+++ b/src/network/access/http2/huffman.cpp
@@ -0,0 +1,573 @@
+/****************************************************************************
+**
+** 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 "bitstreams_p.h"
+#include "huffman_p.h"
+
+#include <QtCore/qbytearray.h>
+
+#include <algorithm>
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+namespace HPack
+{
+
+/*
+ The static Huffman code used here was extracted from:
+ https://http2.github.io/http2-spec/compression.html#huffman.code
+
+ This code was generated from statistics obtained on a large
+ sample of HTTP headers. It is a canonical Huffman code
+ with some tweaking to ensure that no symbol has a unique
+ code length. All codes were left-aligned - for implementation
+ convenience.
+
+ Using binary trees to implement decoding would be prohibitively
+ expensive (both memory and time-wise). Instead we use a table-based
+ approach and any given code itself works as an index into such table(s).
+ We have 256 possible byte values and code lengths in
+ a range [5, 26]. This would require a huge table (and most of entries
+ would be 'wasted', since we only have to encode 256 elements).
+ Instead we use multi-level tables. The first level table
+ is using 9-bit length index; some entries in this table are 'terminal',
+ some reference the next level table(s).
+
+ For example, bytes with values 48 and 49 (ASCII codes for '0' and '1')
+ both have code length 5, Huffman codes are: 00000 and 00001. They
+ both are placed in the 'root' table,
+ the 'root' table has index length == 9:
+ [00000 | 4 remaining bits]
+ ...
+ [00001 | 4 remaining bits]
+
+ All entires with indices between these two will 'point' to value 48
+ with bitLength == 5 so that bit stream (for example) 000001010 will be
+ decoded as: 48 + "put 1010 back into bitstream".
+
+ A good description can be found here:
+ http://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art007
+ or just google "Efficient Huffman Decoding".
+ Also see comments below about 'filling holes'.
+*/
+
+namespace
+{
+
+const CodeEntry staticHuffmanCodeTable[]
+{
+ { 0, 0xffc00000ul, 13}, // 11111111|11000
+ { 1, 0xffffb000ul, 23}, // 11111111|11111111|1011000
+ { 2, 0xfffffe20ul, 28}, // 11111111|11111111|11111110|0010
+ { 3, 0xfffffe30ul, 28}, // 11111111|11111111|11111110|0011
+ { 4, 0xfffffe40ul, 28}, // 11111111|11111111|11111110|0100
+ { 5, 0xfffffe50ul, 28}, // 11111111|11111111|11111110|0101
+ { 6, 0xfffffe60ul, 28}, // 11111111|11111111|11111110|0110
+ { 7, 0xfffffe70ul, 28}, // 11111111|11111111|11111110|0111
+ { 8, 0xfffffe80ul, 28}, // 11111111|11111111|11111110|1000
+ { 9, 0xffffea00ul, 24}, // 11111111|11111111|11101010
+ { 10, 0xfffffff0ul, 30}, // 11111111|11111111|11111111|111100
+ { 11, 0xfffffe90ul, 28}, // 11111111|11111111|11111110|1001
+ { 12, 0xfffffea0ul, 28}, // 11111111|11111111|11111110|1010
+ { 13, 0xfffffff4ul, 30}, // 11111111|11111111|11111111|111101
+ { 14, 0xfffffeb0ul, 28}, // 11111111|11111111|11111110|1011
+ { 15, 0xfffffec0ul, 28}, // 11111111|11111111|11111110|1100
+ { 16, 0xfffffed0ul, 28}, // 11111111|11111111|11111110|1101
+ { 17, 0xfffffee0ul, 28}, // 11111111|11111111|11111110|1110
+ { 18, 0xfffffef0ul, 28}, // 11111111|11111111|11111110|1111
+ { 19, 0xffffff00ul, 28}, // 11111111|11111111|11111111|0000
+ { 20, 0xffffff10ul, 28}, // 11111111|11111111|11111111|0001
+ { 21, 0xffffff20ul, 28}, // 11111111|11111111|11111111|0010
+ { 22, 0xfffffff8ul, 30}, // 11111111|11111111|11111111|111110
+ { 23, 0xffffff30ul, 28}, // 11111111|11111111|11111111|0011
+ { 24, 0xffffff40ul, 28}, // 11111111|11111111|11111111|0100
+ { 25, 0xffffff50ul, 28}, // 11111111|11111111|11111111|0101
+ { 26, 0xffffff60ul, 28}, // 11111111|11111111|11111111|0110
+ { 27, 0xffffff70ul, 28}, // 11111111|11111111|11111111|0111
+ { 28, 0xffffff80ul, 28}, // 11111111|11111111|11111111|1000
+ { 29, 0xffffff90ul, 28}, // 11111111|11111111|11111111|1001
+ { 30, 0xffffffa0ul, 28}, // 11111111|11111111|11111111|1010
+ { 31, 0xffffffb0ul, 28}, // 11111111|11111111|11111111|1011
+ { 32, 0x50000000ul, 6}, // ' ' 010100
+ { 33, 0xfe000000ul, 10}, // '!' 11111110|00
+ { 34, 0xfe400000ul, 10}, // '"' 11111110|01
+ { 35, 0xffa00000ul, 12}, // '#' 11111111|1010
+ { 36, 0xffc80000ul, 13}, // '$' 11111111|11001
+ { 37, 0x54000000ul, 6}, // '%' 010101
+ { 38, 0xf8000000ul, 8}, // '&' 11111000
+ { 39, 0xff400000ul, 11}, // ''' 11111111|010
+ { 40, 0xfe800000ul, 10}, // '(' 11111110|10
+ { 41, 0xfec00000ul, 10}, // ')' 11111110|11
+ { 42, 0xf9000000ul, 8}, // '*' 11111001
+ { 43, 0xff600000ul, 11}, // '+' 11111111|011
+ { 44, 0xfa000000ul, 8}, // ',' 11111010
+ { 45, 0x58000000ul, 6}, // '-' 010110
+ { 46, 0x5c000000ul, 6}, // '.' 010111
+ { 47, 0x60000000ul, 6}, // '/' 011000
+ { 48, 0x00000000ul, 5}, // '0' 00000
+ { 49, 0x08000000ul, 5}, // '1' 00001
+ { 50, 0x10000000ul, 5}, // '2' 00010
+ { 51, 0x64000000ul, 6}, // '3' 011001
+ { 52, 0x68000000ul, 6}, // '4' 011010
+ { 53, 0x6c000000ul, 6}, // '5' 011011
+ { 54, 0x70000000ul, 6}, // '6' 011100
+ { 55, 0x74000000ul, 6}, // '7' 011101
+ { 56, 0x78000000ul, 6}, // '8' 011110
+ { 57, 0x7c000000ul, 6}, // '9' 011111
+ { 58, 0xb8000000ul, 7}, // ':' 1011100
+ { 59, 0xfb000000ul, 8}, // ';' 11111011
+ { 60, 0xfff80000ul, 15}, // '<' 11111111|1111100
+ { 61, 0x80000000ul, 6}, // '=' 100000
+ { 62, 0xffb00000ul, 12}, // '>' 11111111|1011
+ { 63, 0xff000000ul, 10}, // '?' 11111111|00
+ { 64, 0xffd00000ul, 13}, // '@' 11111111|11010
+ { 65, 0x84000000ul, 6}, // 'A' 100001
+ { 66, 0xba000000ul, 7}, // 'B' 1011101
+ { 67, 0xbc000000ul, 7}, // 'C' 1011110
+ { 68, 0xbe000000ul, 7}, // 'D' 1011111
+ { 69, 0xc0000000ul, 7}, // 'E' 1100000
+ { 70, 0xc2000000ul, 7}, // 'F' 1100001
+ { 71, 0xc4000000ul, 7}, // 'G' 1100010
+ { 72, 0xc6000000ul, 7}, // 'H' 1100011
+ { 73, 0xc8000000ul, 7}, // 'I' 1100100
+ { 74, 0xca000000ul, 7}, // 'J' 1100101
+ { 75, 0xcc000000ul, 7}, // 'K' 1100110
+ { 76, 0xce000000ul, 7}, // 'L' 1100111
+ { 77, 0xd0000000ul, 7}, // 'M' 1101000
+ { 78, 0xd2000000ul, 7}, // 'N' 1101001
+ { 79, 0xd4000000ul, 7}, // 'O' 1101010
+ { 80, 0xd6000000ul, 7}, // 'P' 1101011
+ { 81, 0xd8000000ul, 7}, // 'Q' 1101100
+ { 82, 0xda000000ul, 7}, // 'R' 1101101
+ { 83, 0xdc000000ul, 7}, // 'S' 1101110
+ { 84, 0xde000000ul, 7}, // 'T' 1101111
+ { 85, 0xe0000000ul, 7}, // 'U' 1110000
+ { 86, 0xe2000000ul, 7}, // 'V' 1110001
+ { 87, 0xe4000000ul, 7}, // 'W' 1110010
+ { 88, 0xfc000000ul, 8}, // 'X' 11111100
+ { 89, 0xe6000000ul, 7}, // 'Y' 1110011
+ { 90, 0xfd000000ul, 8}, // 'Z' 11111101
+ { 91, 0xffd80000ul, 13}, // '[' 11111111|11011
+ { 92, 0xfffe0000ul, 19}, // '\' 11111111|11111110|000
+ { 93, 0xffe00000ul, 13}, // ']' 11111111|11100
+ { 94, 0xfff00000ul, 14}, // '^' 11111111|111100
+ { 95, 0x88000000ul, 6}, // '_' 100010
+ { 96, 0xfffa0000ul, 15}, // '`' 11111111|1111101
+ { 97, 0x18000000ul, 5}, // 'a' 00011
+ { 98, 0x8c000000ul, 6}, // 'b' 100011
+ { 99, 0x20000000ul, 5}, // 'c' 00100
+ {100, 0x90000000ul, 6}, // 'd' 100100
+ {101, 0x28000000ul, 5}, // 'e' 00101
+ {102, 0x94000000ul, 6}, // 'f' 100101
+ {103, 0x98000000ul, 6}, // 'g' 100110
+ {104, 0x9c000000ul, 6}, // 'h' 100111
+ {105, 0x30000000ul, 5}, // 'i' 00110
+ {106, 0xe8000000ul, 7}, // 'j' 1110100
+ {107, 0xea000000ul, 7}, // 'k' 1110101
+ {108, 0xa0000000ul, 6}, // 'l' 101000
+ {109, 0xa4000000ul, 6}, // 'm' 101001
+ {110, 0xa8000000ul, 6}, // 'n' 101010
+ {111, 0x38000000ul, 5}, // 'o' 00111
+ {112, 0xac000000ul, 6}, // 'p' 101011
+ {113, 0xec000000ul, 7}, // 'q' 1110110
+ {114, 0xb0000000ul, 6}, // 'r' 101100
+ {115, 0x40000000ul, 5}, // 's' 01000
+ {116, 0x48000000ul, 5}, // 't' 01001
+ {117, 0xb4000000ul, 6}, // 'u' 101101
+ {118, 0xee000000ul, 7}, // 'v' 1110111
+ {119, 0xf0000000ul, 7}, // 'w' 1111000
+ {120, 0xf2000000ul, 7}, // 'x' 1111001
+ {121, 0xf4000000ul, 7}, // 'y' 1111010
+ {122, 0xf6000000ul, 7}, // 'z' 1111011
+ {123, 0xfffc0000ul, 15}, // '{' 11111111|1111110
+ {124, 0xff800000ul, 11}, // '|' 11111111|100
+ {125, 0xfff40000ul, 14}, // '}' 11111111|111101
+ {126, 0xffe80000ul, 13}, // '~' 11111111|11101
+ {127, 0xffffffc0ul, 28}, // 11111111|11111111|11111111|1100
+ {128, 0xfffe6000ul, 20}, // 11111111|11111110|0110
+ {129, 0xffff4800ul, 22}, // 11111111|11111111|010010
+ {130, 0xfffe7000ul, 20}, // 11111111|11111110|0111
+ {131, 0xfffe8000ul, 20}, // 11111111|11111110|1000
+ {132, 0xffff4c00ul, 22}, // 11111111|11111111|010011
+ {133, 0xffff5000ul, 22}, // 11111111|11111111|010100
+ {134, 0xffff5400ul, 22}, // 11111111|11111111|010101
+ {135, 0xffffb200ul, 23}, // 11111111|11111111|1011001
+ {136, 0xffff5800ul, 22}, // 11111111|11111111|010110
+ {137, 0xffffb400ul, 23}, // 11111111|11111111|1011010
+ {138, 0xffffb600ul, 23}, // 11111111|11111111|1011011
+ {139, 0xffffb800ul, 23}, // 11111111|11111111|1011100
+ {140, 0xffffba00ul, 23}, // 11111111|11111111|1011101
+ {141, 0xffffbc00ul, 23}, // 11111111|11111111|1011110
+ {142, 0xffffeb00ul, 24}, // 11111111|11111111|11101011
+ {143, 0xffffbe00ul, 23}, // 11111111|11111111|1011111
+ {144, 0xffffec00ul, 24}, // 11111111|11111111|11101100
+ {145, 0xffffed00ul, 24}, // 11111111|11111111|11101101
+ {146, 0xffff5c00ul, 22}, // 11111111|11111111|010111
+ {147, 0xffffc000ul, 23}, // 11111111|11111111|1100000
+ {148, 0xffffee00ul, 24}, // 11111111|11111111|11101110
+ {149, 0xffffc200ul, 23}, // 11111111|11111111|1100001
+ {150, 0xffffc400ul, 23}, // 11111111|11111111|1100010
+ {151, 0xffffc600ul, 23}, // 11111111|11111111|1100011
+ {152, 0xffffc800ul, 23}, // 11111111|11111111|1100100
+ {153, 0xfffee000ul, 21}, // 11111111|11111110|11100
+ {154, 0xffff6000ul, 22}, // 11111111|11111111|011000
+ {155, 0xffffca00ul, 23}, // 11111111|11111111|1100101
+ {156, 0xffff6400ul, 22}, // 11111111|11111111|011001
+ {157, 0xffffcc00ul, 23}, // 11111111|11111111|1100110
+ {158, 0xffffce00ul, 23}, // 11111111|11111111|1100111
+ {159, 0xffffef00ul, 24}, // 11111111|11111111|11101111
+ {160, 0xffff6800ul, 22}, // 11111111|11111111|011010
+ {161, 0xfffee800ul, 21}, // 11111111|11111110|11101
+ {162, 0xfffe9000ul, 20}, // 11111111|11111110|1001
+ {163, 0xffff6c00ul, 22}, // 11111111|11111111|011011
+ {164, 0xffff7000ul, 22}, // 11111111|11111111|011100
+ {165, 0xffffd000ul, 23}, // 11111111|11111111|1101000
+ {166, 0xffffd200ul, 23}, // 11111111|11111111|1101001
+ {167, 0xfffef000ul, 21}, // 11111111|11111110|11110
+ {168, 0xffffd400ul, 23}, // 11111111|11111111|1101010
+ {169, 0xffff7400ul, 22}, // 11111111|11111111|011101
+ {170, 0xffff7800ul, 22}, // 11111111|11111111|011110
+ {171, 0xfffff000ul, 24}, // 11111111|11111111|11110000
+ {172, 0xfffef800ul, 21}, // 11111111|11111110|11111
+ {173, 0xffff7c00ul, 22}, // 11111111|11111111|011111
+ {174, 0xffffd600ul, 23}, // 11111111|11111111|1101011
+ {175, 0xffffd800ul, 23}, // 11111111|11111111|1101100
+ {176, 0xffff0000ul, 21}, // 11111111|11111111|00000
+ {177, 0xffff0800ul, 21}, // 11111111|11111111|00001
+ {178, 0xffff8000ul, 22}, // 11111111|11111111|100000
+ {179, 0xffff1000ul, 21}, // 11111111|11111111|00010
+ {180, 0xffffda00ul, 23}, // 11111111|11111111|1101101
+ {181, 0xffff8400ul, 22}, // 11111111|11111111|100001
+ {182, 0xffffdc00ul, 23}, // 11111111|11111111|1101110
+ {183, 0xffffde00ul, 23}, // 11111111|11111111|1101111
+ {184, 0xfffea000ul, 20}, // 11111111|11111110|1010
+ {185, 0xffff8800ul, 22}, // 11111111|11111111|100010
+ {186, 0xffff8c00ul, 22}, // 11111111|11111111|100011
+ {187, 0xffff9000ul, 22}, // 11111111|11111111|100100
+ {188, 0xffffe000ul, 23}, // 11111111|11111111|1110000
+ {189, 0xffff9400ul, 22}, // 11111111|11111111|100101
+ {190, 0xffff9800ul, 22}, // 11111111|11111111|100110
+ {191, 0xffffe200ul, 23}, // 11111111|11111111|1110001
+ {192, 0xfffff800ul, 26}, // 11111111|11111111|11111000|00
+ {193, 0xfffff840ul, 26}, // 11111111|11111111|11111000|01
+ {194, 0xfffeb000ul, 20}, // 11111111|11111110|1011
+ {195, 0xfffe2000ul, 19}, // 11111111|11111110|001
+ {196, 0xffff9c00ul, 22}, // 11111111|11111111|100111
+ {197, 0xffffe400ul, 23}, // 11111111|11111111|1110010
+ {198, 0xffffa000ul, 22}, // 11111111|11111111|101000
+ {199, 0xfffff600ul, 25}, // 11111111|11111111|11110110|0
+ {200, 0xfffff880ul, 26}, // 11111111|11111111|11111000|10
+ {201, 0xfffff8c0ul, 26}, // 11111111|11111111|11111000|11
+ {202, 0xfffff900ul, 26}, // 11111111|11111111|11111001|00
+ {203, 0xfffffbc0ul, 27}, // 11111111|11111111|11111011|110
+ {204, 0xfffffbe0ul, 27}, // 11111111|11111111|11111011|111
+ {205, 0xfffff940ul, 26}, // 11111111|11111111|11111001|01
+ {206, 0xfffff100ul, 24}, // 11111111|11111111|11110001
+ {207, 0xfffff680ul, 25}, // 11111111|11111111|11110110|1
+ {208, 0xfffe4000ul, 19}, // 11111111|11111110|010
+ {209, 0xffff1800ul, 21}, // 11111111|11111111|00011
+ {210, 0xfffff980ul, 26}, // 11111111|11111111|11111001|10
+ {211, 0xfffffc00ul, 27}, // 11111111|11111111|11111100|000
+ {212, 0xfffffc20ul, 27}, // 11111111|11111111|11111100|001
+ {213, 0xfffff9c0ul, 26}, // 11111111|11111111|11111001|11
+ {214, 0xfffffc40ul, 27}, // 11111111|11111111|11111100|010
+ {215, 0xfffff200ul, 24}, // 11111111|11111111|11110010
+ {216, 0xffff2000ul, 21}, // 11111111|11111111|00100
+ {217, 0xffff2800ul, 21}, // 11111111|11111111|00101
+ {218, 0xfffffa00ul, 26}, // 11111111|11111111|11111010|00
+ {219, 0xfffffa40ul, 26}, // 11111111|11111111|11111010|01
+ {220, 0xffffffd0ul, 28}, // 11111111|11111111|11111111|1101
+ {221, 0xfffffc60ul, 27}, // 11111111|11111111|11111100|011
+ {222, 0xfffffc80ul, 27}, // 11111111|11111111|11111100|100
+ {223, 0xfffffca0ul, 27}, // 11111111|11111111|11111100|101
+ {224, 0xfffec000ul, 20}, // 11111111|11111110|1100
+ {225, 0xfffff300ul, 24}, // 11111111|11111111|11110011
+ {226, 0xfffed000ul, 20}, // 11111111|11111110|1101
+ {227, 0xffff3000ul, 21}, // 11111111|11111111|00110
+ {228, 0xffffa400ul, 22}, // 11111111|11111111|101001
+ {229, 0xffff3800ul, 21}, // 11111111|11111111|00111
+ {230, 0xffff4000ul, 21}, // 11111111|11111111|01000
+ {231, 0xffffe600ul, 23}, // 11111111|11111111|1110011
+ {232, 0xffffa800ul, 22}, // 11111111|11111111|101010
+ {233, 0xffffac00ul, 22}, // 11111111|11111111|101011
+ {234, 0xfffff700ul, 25}, // 11111111|11111111|11110111|0
+ {235, 0xfffff780ul, 25}, // 11111111|11111111|11110111|1
+ {236, 0xfffff400ul, 24}, // 11111111|11111111|11110100
+ {237, 0xfffff500ul, 24}, // 11111111|11111111|11110101
+ {238, 0xfffffa80ul, 26}, // 11111111|11111111|11111010|10
+ {239, 0xffffe800ul, 23}, // 11111111|11111111|1110100
+ {240, 0xfffffac0ul, 26}, // 11111111|11111111|11111010|11
+ {241, 0xfffffcc0ul, 27}, // 11111111|11111111|11111100|110
+ {242, 0xfffffb00ul, 26}, // 11111111|11111111|11111011|00
+ {243, 0xfffffb40ul, 26}, // 11111111|11111111|11111011|01
+ {244, 0xfffffce0ul, 27}, // 11111111|11111111|11111100|111
+ {245, 0xfffffd00ul, 27}, // 11111111|11111111|11111101|000
+ {246, 0xfffffd20ul, 27}, // 11111111|11111111|11111101|001
+ {247, 0xfffffd40ul, 27}, // 11111111|11111111|11111101|010
+ {248, 0xfffffd60ul, 27}, // 11111111|11111111|11111101|011
+ {249, 0xffffffe0ul, 28}, // 11111111|11111111|11111111|1110
+ {250, 0xfffffd80ul, 27}, // 11111111|11111111|11111101|100
+ {251, 0xfffffda0ul, 27}, // 11111111|11111111|11111101|101
+ {252, 0xfffffdc0ul, 27}, // 11111111|11111111|11111101|110
+ {253, 0xfffffde0ul, 27}, // 11111111|11111111|11111101|111
+ {254, 0xfffffe00ul, 27}, // 11111111|11111111|11111110|000
+ {255, 0xfffffb80ul, 26}, // 11111111|11111111|11111011|10
+ {256, 0xfffffffcul, 30} // EOS 11111111|11111111|11111111|111111
+};
+
+void write_huffman_code(BitOStream &outputStream, const CodeEntry &code)
+{
+ // Append octet by octet.
+ auto bitLength = code.bitLength;
+ const auto hc = code.huffmanCode >> (32 - bitLength);
+
+ if (bitLength > 24) {
+ outputStream.writeBits(uchar(hc >> 24), bitLength - 24);
+ bitLength = 24;
+ }
+
+ if (bitLength > 16) {
+ outputStream.writeBits(uchar(hc >> 16), bitLength - 16);
+ bitLength = 16;
+ }
+
+ if (bitLength > 8) {
+ outputStream.writeBits(uchar(hc >> 8), bitLength - 8);
+ bitLength = 8;
+ }
+
+ outputStream.writeBits(uchar(hc), bitLength);
+}
+
+}
+
+// That's from HPACK's specs - we deal with octets.
+static_assert(std::numeric_limits<uchar>::digits == 8, "octets expected");
+
+quint64 huffman_encoded_bit_length(const QByteArray &inputData)
+{
+ quint64 bitLength = 0;
+ for (int i = 0, e = inputData.size(); i < e; ++i)
+ bitLength += staticHuffmanCodeTable[int(inputData[i])].bitLength;
+
+ return bitLength;
+}
+
+void huffman_encode_string(const QByteArray &inputData, BitOStream &outputStream)
+{
+ for (int i = 0, e = inputData.size(); i < e; ++i)
+ write_huffman_code(outputStream, staticHuffmanCodeTable[int(inputData[i])]);
+
+ // Pad bits ...
+ if (outputStream.bitLength() % 8)
+ outputStream.writeBits(0xff, 8 - outputStream.bitLength() % 8);
+}
+
+bool padding_is_valid(quint32 chunk, quint32 nBits)
+{
+ Q_ASSERT(nBits);
+
+ // HPACK, 5.2: "A padding strictly longer than 7 bits MUST be
+ // treated as a decoding error."
+ if (nBits > 7)
+ return false;
+ // HPACK, 5.2:
+ // "A padding not corresponding to the most significant bits
+ // of the code for the EOS symbol MUST be treated as a decoding error."
+ return (chunk >> (32 - nBits)) == quint32((1 << nBits) - 1);
+}
+
+HuffmanDecoder::HuffmanDecoder()
+ : minCodeLength()
+{
+ const auto nCodes = sizeof staticHuffmanCodeTable / sizeof staticHuffmanCodeTable[0];
+
+ std::vector<CodeEntry> symbols(staticHuffmanCodeTable, staticHuffmanCodeTable + nCodes);
+ // Now we sort: by bit length first (in the descending order) and by the symbol
+ // value (descending). Descending order: to make sure we do not create prefix tables with
+ // short 'indexLength' first and having longer codes that do not fit into such tables later.
+ std::sort(symbols.begin(), symbols.end(), [](const CodeEntry &code1, const CodeEntry &code2) {
+ if (code1.bitLength == code2.bitLength)
+ return code1.byteValue > code2.byteValue;
+ return code1.bitLength > code2.bitLength;
+ });
+
+ minCodeLength = symbols.back().bitLength; // The shortest one, currently it's 5.
+
+ // TODO: add a verification - Huffman codes
+ // within a given bit length range also
+ // should be in descending order.
+
+ // Initialize 'prefixTables' and 'tableData'.
+ addTable(0, quint32(BitConstants::rootPrefix)); // 'root' table.
+
+ for (const auto &s : symbols) {
+ quint32 tableIndex = 0;
+ while (true) {
+ Q_ASSERT(tableIndex < prefixTables.size());
+ // Note, by value - since prefixTables will be updated in between.
+ const auto table = prefixTables[tableIndex];
+ // We skip prefixed bits (if any) and use indexed bits only:
+ const auto entryIndex = s.huffmanCode << table.prefixLength >> (32 - table.indexLength);
+ // Again, by value.
+ PrefixTableEntry entry = tableEntry(table, entryIndex);
+ // How many bits were coded by previous tables and this table:
+ const auto codedLength = table.prefixLength + table.indexLength;
+ if (codedLength < s.bitLength) {
+ // We have to add a new prefix table ... (if it's not done yet).
+ if (!entry.bitLength) {
+ entry.nextTable = addTable(codedLength, std::min<quint32>(quint32(BitConstants::childPrefix),
+ s.bitLength - codedLength));
+ entry.bitLength = s.bitLength;
+ entry.byteValue = s.byteValue;
+ setTableEntry(table, entryIndex, entry);
+ }
+ tableIndex = entry.nextTable;
+ } else {
+ // We found the slot for our code (terminal entry):
+ entry.byteValue = s.byteValue;
+ entry.bitLength = s.bitLength;
+ // Refer to our own table as 'nextTable':
+ entry.nextTable = tableIndex;
+ setTableEntry(table, entryIndex, entry);
+ break;
+ }
+ }
+ }
+
+ // Now, we have a table(s) and have to fill 'holes' to
+ // 'fix' terminal entries.
+ for (const auto &table : prefixTables) {
+ const quint32 codedLength = table.prefixLength + table.indexLength;
+ for (quint32 j = 0; j < table.size();) {
+ const PrefixTableEntry &entry = tableEntry(table, j);
+ if (entry.bitLength && entry.bitLength < codedLength) {
+ const quint32 range = 1 << (codedLength - entry.bitLength);
+ for (quint32 k = 1; k < range; ++k)
+ setTableEntry(table, j + k, entry);
+ j += range;
+ } else {
+ ++j;
+ }
+ }
+ }
+}
+
+bool HuffmanDecoder::decodeStream(BitIStream &inputStream, QByteArray &outputBuffer)
+{
+ while (true) {
+ quint32 chunk = 0;
+ const quint32 readBits = inputStream.peekBits(inputStream.streamOffset(), 32, &chunk);
+ if (!readBits)
+ return !inputStream.hasMoreBits();
+
+ if (readBits < minCodeLength) {
+ inputStream.skipBits(readBits);
+ return padding_is_valid(chunk, readBits);
+ }
+
+ quint32 tableIndex = 0;
+ const PrefixTable *table = &prefixTables[tableIndex];
+ quint32 entryIndex = chunk >> (32 - table->indexLength);
+ PrefixTableEntry entry = tableEntry(*table, entryIndex);
+
+ while (true) {
+ if (entry.nextTable == tableIndex)
+ break;
+
+ tableIndex = entry.nextTable;
+ table = &prefixTables[tableIndex];
+ entryIndex = chunk << table->prefixLength >> (32 - table->indexLength);
+ entry = tableEntry(*table, entryIndex);
+ }
+
+ if (entry.bitLength > readBits) {
+ inputStream.skipBits(readBits);
+ return padding_is_valid(chunk, readBits);
+ }
+
+ if (!entry.bitLength || entry.byteValue == 256) {
+ //EOS (256) == compression error (HPACK).
+ inputStream.skipBits(readBits);
+ return false;
+ }
+
+ outputBuffer.append(entry.byteValue);
+ inputStream.skipBits(entry.bitLength);
+ }
+
+ return false;
+}
+
+quint32 HuffmanDecoder::addTable(quint32 prefix, quint32 index)
+{
+ PrefixTable newTable{prefix, index};
+ newTable.offset = quint32(tableData.size());
+ prefixTables.push_back(newTable);
+ // Add entries for this table:
+ tableData.resize(tableData.size() + newTable.size());
+
+ return quint32(prefixTables.size() - 1);
+}
+
+PrefixTableEntry HuffmanDecoder::tableEntry(const PrefixTable &table, quint32 index)
+{
+ Q_ASSERT(index < table.size());
+ return tableData[table.offset + index];
+}
+
+void HuffmanDecoder::setTableEntry(const PrefixTable &table, quint32 index,
+ const PrefixTableEntry &entry)
+{
+ Q_ASSERT(index < table.size());
+ tableData[table.offset + index] = entry;
+}
+
+bool huffman_decode_string(BitIStream &inputStream, QByteArray *outputBuffer)
+{
+ Q_ASSERT(outputBuffer);
+
+ static HuffmanDecoder decoder;
+ return decoder.decodeStream(inputStream, *outputBuffer);
+}
+
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/http2/huffman_p.h b/src/network/access/http2/huffman_p.h
new file mode 100644
index 0000000000..7195661664
--- /dev/null
+++ b/src/network/access/http2/huffman_p.h
@@ -0,0 +1,182 @@
+/****************************************************************************
+**
+** 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 HUFFMAN_P_H
+#define HUFFMAN_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QByteArray;
+
+namespace HPack
+{
+
+struct CodeEntry
+{
+ CodeEntry() : byteValue(),
+ huffmanCode(),
+ bitLength()
+ {
+ }
+
+ CodeEntry(quint32 val, quint32 code, quint32 len)
+ : byteValue(val),
+ huffmanCode(code),
+ bitLength(len)
+ {
+ }
+
+ quint32 byteValue;
+ quint32 huffmanCode;
+ quint32 bitLength;
+};
+
+class BitOStream;
+
+quint64 huffman_encoded_bit_length(const QByteArray &inputData);
+void huffman_encode_string(const QByteArray &inputData, BitOStream &outputStream);
+
+// PrefixTable:
+// Huffman codes with a small bit length
+// fit into a table (these are 'terminal' symbols),
+// codes with longer codes require additional
+// tables, so several symbols will have the same index
+// in a table - pointing into the next table.
+// Every table has an 'indexLength' - that's
+// how many bits can fit in table's indices +
+// 'prefixLength' - how many bits were addressed
+// by its 'parent' table(s).
+// All PrefixTables are kept in 'prefixTables' array.
+// PrefixTable itself does not have any entries,
+// it just holds table's prefix/index + 'offset' -
+// there table's data starts in an array of all
+// possible entries ('tableData').
+
+struct PrefixTable
+{
+ PrefixTable()
+ : prefixLength(),
+ indexLength(),
+ offset()
+ {
+ }
+
+ PrefixTable(quint32 prefix, quint32 index)
+ : prefixLength(prefix),
+ indexLength(index),
+ offset()
+ {
+ }
+
+ quint32 size()const
+ {
+ // Number of entries table contains:
+ return 1 << indexLength;
+ }
+
+ quint32 prefixLength;
+ quint32 indexLength;
+ quint32 offset;
+};
+
+// Table entry is either a terminal entry (thus probably the code found)
+// or points into another table ('nextTable' - index into
+// 'prefixTables' array). If it's a terminal, 'nextTable' index
+// refers to the same table.
+
+struct PrefixTableEntry
+{
+ PrefixTableEntry()
+ : bitLength(),
+ nextTable(),
+ byteValue()
+ {
+ }
+
+ quint32 bitLength;
+ quint32 nextTable;
+ quint32 byteValue;
+};
+
+class BitIStream;
+
+class HuffmanDecoder
+{
+public:
+ enum class BitConstants
+ {
+ rootPrefix = 9,
+ childPrefix = 6
+ };
+
+ HuffmanDecoder();
+
+ bool decodeStream(BitIStream &inputStream, QByteArray &outputBuffer);
+
+private:
+ quint32 addTable(quint32 prefixLength, quint32 indexLength);
+ PrefixTableEntry tableEntry(const PrefixTable &table, quint32 index);
+ void setTableEntry(const PrefixTable &table, quint32 index, const PrefixTableEntry &entry);
+
+ std::vector<PrefixTable> prefixTables;
+ std::vector<PrefixTableEntry> tableData;
+ quint32 minCodeLength;
+};
+
+bool huffman_decode_string(BitIStream &inputStream, QByteArray *outputBuffer);
+
+} // namespace HPack
+
+QT_END_NAMESPACE
+
+#endif
+
diff --git a/src/network/access/qabstractnetworkcache.h b/src/network/access/qabstractnetworkcache.h
index 6b80147757..33b0bc4ce3 100644
--- a/src/network/access/qabstractnetworkcache.h
+++ b/src/network/access/qabstractnetworkcache.h
@@ -40,6 +40,7 @@
#ifndef QABSTRACTNETWORKCACHE_H
#define QABSTRACTNETWORKCACHE_H
+#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/qobject.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qpair.h>
diff --git a/src/network/access/qabstractnetworkcache_p.h b/src/network/access/qabstractnetworkcache_p.h
index 7be89f22b6..fee723e315 100644
--- a/src/network/access/qabstractnetworkcache_p.h
+++ b/src/network/access/qabstractnetworkcache_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "private/qobject_p.h"
QT_BEGIN_NAMESPACE
diff --git a/src/network/access/qabstractprotocolhandler_p.h b/src/network/access/qabstractprotocolhandler_p.h
index a05df3e429..30814d6737 100644
--- a/src/network/access/qabstractprotocolhandler_p.h
+++ b/src/network/access/qabstractprotocolhandler_p.h
@@ -51,9 +51,9 @@
// We mean it.
//
-#ifndef QT_NO_HTTP
+#include <QtNetwork/private/qtnetworkglobal_p.h>
-#include <QtCore/qglobal.h>
+#ifndef QT_NO_HTTP
QT_BEGIN_NAMESPACE
diff --git a/src/network/access/qftp_p.h b/src/network/access/qftp_p.h
index 37f8f2f8d2..6cf5116798 100644
--- a/src/network/access/qftp_p.h
+++ b/src/network/access/qftp_p.h
@@ -48,9 +48,10 @@
// We mean it.
//
-#ifndef QFTP_H
-#define QFTP_H
+#ifndef QFTP_P_H
+#define QFTP_P_H
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include <QtCore/qstring.h>
#include <private/qurlinfo_p.h>
#include <QtCore/qobject.h>
@@ -172,4 +173,4 @@ private:
QT_END_NAMESPACE
-#endif // QFTP_H
+#endif // QFTP_P_H
diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp
new file mode 100644
index 0000000000..5bbff9ea29
--- /dev/null
+++ b/src/network/access/qhttp2protocolhandler.cpp
@@ -0,0 +1,1217 @@
+/****************************************************************************
+**
+** 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 "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..b146e37dd3
--- /dev/null
+++ b/src/network/access/qhttp2protocolhandler_p.h
@@ -0,0 +1,207 @@
+/****************************************************************************
+**
+** 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
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/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/qhttpmultipart.h b/src/network/access/qhttpmultipart.h
index cbf28c083e..6d4531b099 100644
--- a/src/network/access/qhttpmultipart.h
+++ b/src/network/access/qhttpmultipart.h
@@ -40,6 +40,7 @@
#ifndef QHTTPMULTIPART_H
#define QHTTPMULTIPART_H
+#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/QSharedDataPointer>
#include <QtCore/QByteArray>
#include <QtCore/QIODevice>
diff --git a/src/network/access/qhttpmultipart_p.h b/src/network/access/qhttpmultipart_p.h
index 875f52f8e3..a03df9cb13 100644
--- a/src/network/access/qhttpmultipart_p.h
+++ b/src/network/access/qhttpmultipart_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "QtCore/qshareddata.h"
#include "qnetworkrequest_p.h" // for deriving QHttpPartPrivate from QNetworkHeadersPrivate
#include "private/qobject_p.h"
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 79f418f675..09cea8e769 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -60,6 +60,7 @@
# include <QtNetwork/qsslkey.h>
# include <QtNetwork/qsslcipher.h>
# include <QtNetwork/qsslconfiguration.h>
+# include <QtNetwork/qsslerror.h>
#endif
@@ -82,7 +83,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 +1013,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 e05bc1df74..cad0ab1ac4 100644
--- a/src/network/access/qhttpnetworkconnection_p.h
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -50,6 +50,8 @@
//
// We mean it.
//
+
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include <QtNetwork/qnetworkrequest.h>
#include <QtNetwork/qnetworkreply.h>
#include <QtNetwork/qabstractsocket.h>
@@ -70,17 +72,6 @@
#ifndef QT_NO_HTTP
-#ifndef QT_NO_SSL
-#ifndef QT_NO_OPENSSL
-# include <private/qsslcontext_openssl_p.h>
-#endif
-# include <private/qsslsocket_p.h>
-# include <QtNetwork/qsslsocket.h>
-# include <QtNetwork/qsslerror.h>
-#else
-# include <QtNetwork/qtcpsocket.h>
-#endif
-
QT_BEGIN_NAMESPACE
class QHttpNetworkRequest;
@@ -88,6 +79,10 @@ class QHttpNetworkReply;
class QHttpThreadDelegate;
class QByteArray;
class QHostInfo;
+#ifndef QT_NO_SSL
+class QSslConfiguration;
+class QSslContext;
+#endif // !QT_NO_SSL
class QHttpNetworkConnectionPrivate;
class Q_AUTOTEST_EXPORT QHttpNetworkConnection : public QObject
@@ -97,7 +92,8 @@ public:
enum ConnectionType {
ConnectionTypeHTTP,
- ConnectionTypeSPDY
+ ConnectionTypeSPDY,
+ ConnectionTypeHTTP2
};
#ifndef QT_NO_BEARERMANAGEMENT
@@ -159,6 +155,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..3a780f636b 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -47,10 +47,12 @@
#ifndef QT_NO_HTTP
+#include <private/qhttp2protocolhandler_p.h>
#include <private/qhttpprotocolhandler_p.h>
#include <private/qspdyprotocolhandler_p.h>
#ifndef QT_NO_SSL
+# include <private/qsslsocket_p.h>
# include <QtNetwork/qsslkey.h>
# include <QtNetwork/qsslcipher.h>
# include <QtNetwork/qsslconfiguration.h>
@@ -971,7 +973,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 +1006,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 +1047,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 +1074,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/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
index b3b52a7f42..d7d5d86a7a 100644
--- a/src/network/access/qhttpnetworkconnectionchannel_p.h
+++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
@@ -50,6 +50,8 @@
//
// We mean it.
//
+
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include <QtNetwork/qnetworkrequest.h>
#include <QtNetwork/qnetworkreply.h>
#include <QtNetwork/qabstractsocket.h>
diff --git a/src/network/access/qhttpnetworkheader_p.h b/src/network/access/qhttpnetworkheader_p.h
index 3eaab587a0..89169b9331 100644
--- a/src/network/access/qhttpnetworkheader_p.h
+++ b/src/network/access/qhttpnetworkheader_p.h
@@ -50,6 +50,9 @@
//
// We mean it.
//
+
+#include <QtNetwork/private/qtnetworkglobal_p.h>
+
#ifndef QT_NO_HTTP
#include <qshareddata.h>
diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp
index 3601c36bc2..24ada3a81f 100644
--- a/src/network/access/qhttpnetworkreply.cpp
+++ b/src/network/access/qhttpnetworkreply.cpp
@@ -484,8 +484,7 @@ qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket)
}
// is this a valid reply?
- if (fragment.length() >= 5 && !fragment.startsWith("HTTP/"))
- {
+ if (fragment.length() == 5 && !fragment.startsWith("HTTP/")) {
fragment.clear();
return -1;
}
@@ -739,6 +738,8 @@ qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuff
#ifndef QT_NO_COMPRESS
int QHttpNetworkReplyPrivate::initializeInflateStream()
{
+ Q_ASSERT(inflateStrm);
+
inflateStrm->zalloc = Z_NULL;
inflateStrm->zfree = Z_NULL;
inflateStrm->opaque = Z_NULL;
diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h
index da39633dd1..f3b007f594 100644
--- a/src/network/access/qhttpnetworkreply_p.h
+++ b/src/network/access/qhttpnetworkreply_p.h
@@ -50,9 +50,13 @@
//
// We mean it.
//
-#include <qplatformdefs.h>
+
+#include <QtNetwork/private/qtnetworkglobal_p.h>
+
#ifndef QT_NO_HTTP
+#include <qplatformdefs.h>
+
#ifndef QT_NO_COMPRESS
struct z_stream_s;
#endif
@@ -176,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 d9540e6369..d1abb76e28 100644
--- a/src/network/access/qhttpnetworkrequest_p.h
+++ b/src/network/access/qhttpnetworkrequest_p.h
@@ -50,6 +50,8 @@
//
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
+
#ifndef QT_NO_HTTP
#include <private/qhttpnetworkheader_p.h>
@@ -115,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);
@@ -142,6 +147,7 @@ private:
friend class QHttpNetworkConnectionPrivate;
friend class QHttpNetworkConnectionChannel;
friend class QHttpProtocolHandler;
+ friend class QHttp2ProtocolHandler;
friend class QSpdyProtocolHandler;
};
@@ -163,6 +169,7 @@ public:
bool autoDecompress;
bool pipeliningAllowed;
bool spdyAllowed;
+ bool http2Allowed;
bool withCredentials;
bool ssl;
bool preConnect;
diff --git a/src/network/access/qhttpprotocolhandler_p.h b/src/network/access/qhttpprotocolhandler_p.h
index b13993778c..863b988be3 100644
--- a/src/network/access/qhttpprotocolhandler_p.h
+++ b/src/network/access/qhttpprotocolhandler_p.h
@@ -52,6 +52,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include <private/qabstractprotocolhandler_p.h>
#ifndef QT_NO_HTTP
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/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h
index cec125d7a5..64c58cf648 100644
--- a/src/network/access/qhttpthreaddelegate_p.h
+++ b/src/network/access/qhttpthreaddelegate_p.h
@@ -52,6 +52,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include <QObject>
#include <QThreadStorage>
#include <QNetworkProxy>
diff --git a/src/network/access/qnetworkaccessauthenticationmanager_p.h b/src/network/access/qnetworkaccessauthenticationmanager_p.h
index 3d1cd6b4fb..548675728f 100644
--- a/src/network/access/qnetworkaccessauthenticationmanager_p.h
+++ b/src/network/access/qnetworkaccessauthenticationmanager_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkaccessmanager.h"
#include "qnetworkaccesscache_p.h"
#include "qnetworkaccessbackend_p.h"
diff --git a/src/network/access/qnetworkaccessbackend_p.h b/src/network/access/qnetworkaccessbackend_p.h
index 47f5872235..7f39c942a3 100644
--- a/src/network/access/qnetworkaccessbackend_p.h
+++ b/src/network/access/qnetworkaccessbackend_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkreplyimpl_p.h"
#include "QtCore/qobject.h"
diff --git a/src/network/access/qnetworkaccesscache_p.h b/src/network/access/qnetworkaccesscache_p.h
index 2337dd10af..3732b5cbb4 100644
--- a/src/network/access/qnetworkaccesscache_p.h
+++ b/src/network/access/qnetworkaccesscache_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "QtCore/qobject.h"
#include "QtCore/qbasictimer.h"
#include "QtCore/qbytearray.h"
diff --git a/src/network/access/qnetworkaccesscachebackend_p.h b/src/network/access/qnetworkaccesscachebackend_p.h
index c9b8c84579..8db1a6b1d5 100644
--- a/src/network/access/qnetworkaccesscachebackend_p.h
+++ b/src/network/access/qnetworkaccesscachebackend_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkaccessbackend_p.h"
#include "qnetworkrequest.h"
#include "qnetworkreply.h"
diff --git a/src/network/access/qnetworkaccessdebugpipebackend_p.h b/src/network/access/qnetworkaccessdebugpipebackend_p.h
index 6f8e248880..1d1af61dbd 100644
--- a/src/network/access/qnetworkaccessdebugpipebackend_p.h
+++ b/src/network/access/qnetworkaccessdebugpipebackend_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkaccessbackend_p.h"
#include "qnetworkrequest.h"
#include "qnetworkreply.h"
diff --git a/src/network/access/qnetworkaccessfilebackend_p.h b/src/network/access/qnetworkaccessfilebackend_p.h
index dec90062e4..081ff2b9e7 100644
--- a/src/network/access/qnetworkaccessfilebackend_p.h
+++ b/src/network/access/qnetworkaccessfilebackend_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkaccessbackend_p.h"
#include "qnetworkrequest.h"
#include "qnetworkreply.h"
diff --git a/src/network/access/qnetworkaccessftpbackend_p.h b/src/network/access/qnetworkaccessftpbackend_p.h
index c5beaf94e8..cdf6b7a07f 100644
--- a/src/network/access/qnetworkaccessftpbackend_p.h
+++ b/src/network/access/qnetworkaccessftpbackend_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkaccessbackend_p.h"
#include "qnetworkaccesscache_p.h"
#include "qnetworkrequest.h"
diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp
index 587ab27a0f..9fee172283 100644
--- a/src/network/access/qnetworkaccessmanager.cpp
+++ b/src/network/access/qnetworkaccessmanager.cpp
@@ -1516,27 +1516,35 @@ void QNetworkAccessManagerPrivate::clearCache(QNetworkAccessManager *manager)
manager->d_func()->objectCache.clear();
manager->d_func()->authenticationManager->clearCache();
- if (manager->d_func()->httpThread) {
- manager->d_func()->httpThread->quit();
- manager->d_func()->httpThread->wait(5000);
- if (manager->d_func()->httpThread->isFinished())
- delete manager->d_func()->httpThread;
- else
- QObject::connect(manager->d_func()->httpThread, SIGNAL(finished()), manager->d_func()->httpThread, SLOT(deleteLater()));
- manager->d_func()->httpThread = 0;
- }
+ manager->d_func()->destroyThread();
}
QNetworkAccessManagerPrivate::~QNetworkAccessManagerPrivate()
{
- if (httpThread) {
- httpThread->quit();
- httpThread->wait(5000);
- if (httpThread->isFinished())
- delete httpThread;
+ destroyThread();
+}
+
+QThread * QNetworkAccessManagerPrivate::createThread()
+{
+ if (!thread) {
+ thread = new QThread;
+ thread->setObjectName(QStringLiteral("QNetworkAccessManager thread"));
+ thread->start();
+ }
+ Q_ASSERT(thread);
+ return thread;
+}
+
+void QNetworkAccessManagerPrivate::destroyThread()
+{
+ if (thread) {
+ thread->quit();
+ thread->wait(5000);
+ if (thread->isFinished())
+ delete thread;
else
- QObject::connect(httpThread, SIGNAL(finished()), httpThread, SLOT(deleteLater()));
- httpThread = 0;
+ QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
+ thread = 0;
}
}
diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h
index cba5cc09d6..69753e3e4d 100644
--- a/src/network/access/qnetworkaccessmanager.h
+++ b/src/network/access/qnetworkaccessmanager.h
@@ -40,6 +40,7 @@
#ifndef QNETWORKACCESSMANAGER_H
#define QNETWORKACCESSMANAGER_H
+#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/QObject>
#ifndef QT_NO_SSL
#include <QtNetwork/QSslConfiguration>
@@ -173,6 +174,7 @@ private:
friend class QNetworkReplyImplPrivate;
friend class QNetworkReplyHttpImpl;
friend class QNetworkReplyHttpImplPrivate;
+ friend class QNetworkReplyFileImpl;
Q_DECLARE_PRIVATE(QNetworkAccessManager)
Q_PRIVATE_SLOT(d_func(), void _q_replyFinished())
diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h
index 44cf253368..bb4641ab8b 100644
--- a/src/network/access/qnetworkaccessmanager_p.h
+++ b/src/network/access/qnetworkaccessmanager_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkaccessmanager.h"
#include "qnetworkaccesscache_p.h"
#include "qnetworkaccessbackend_p.h"
@@ -74,7 +75,7 @@ class QNetworkAccessManagerPrivate: public QObjectPrivate
public:
QNetworkAccessManagerPrivate()
: networkCache(0), cookieJar(0),
- httpThread(0),
+ thread(0),
#ifndef QT_NO_NETWORKPROXY
proxyFactory(0),
#endif
@@ -107,6 +108,9 @@ public:
}
~QNetworkAccessManagerPrivate();
+ QThread * createThread();
+ void destroyThread();
+
void _q_replyFinished();
void _q_replyEncrypted();
void _q_replySslErrors(const QList<QSslError> &errors);
@@ -163,7 +167,7 @@ public:
QNetworkCookieJar *cookieJar;
- QThread *httpThread;
+ QThread *thread;
#ifndef QT_NO_NETWORKPROXY
diff --git a/src/network/access/qnetworkcookie.cpp b/src/network/access/qnetworkcookie.cpp
index 21dc12829a..7a538cbf08 100644
--- a/src/network/access/qnetworkcookie.cpp
+++ b/src/network/access/qnetworkcookie.cpp
@@ -970,7 +970,7 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt
if (ok) {
if (secs <= 0) {
//earliest representable time (RFC6265 section 5.2.2)
- cookie.setExpirationDate(QDateTime::fromTime_t(0));
+ cookie.setExpirationDate(QDateTime::fromSecsSinceEpoch(0));
} else {
cookie.setExpirationDate(now.addSecs(secs));
}
diff --git a/src/network/access/qnetworkcookie.h b/src/network/access/qnetworkcookie.h
index 8f4b26465a..e462b98555 100644
--- a/src/network/access/qnetworkcookie.h
+++ b/src/network/access/qnetworkcookie.h
@@ -40,6 +40,7 @@
#ifndef QNETWORKCOOKIE_H
#define QNETWORKCOOKIE_H
+#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/QSharedDataPointer>
#include <QtCore/QList>
#include <QtCore/QMetaType>
diff --git a/src/network/access/qnetworkcookie_p.h b/src/network/access/qnetworkcookie_p.h
index 61f04e7177..13538ad243 100644
--- a/src/network/access/qnetworkcookie_p.h
+++ b/src/network/access/qnetworkcookie_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "QtCore/qdatetime.h"
QT_BEGIN_NAMESPACE
diff --git a/src/network/access/qnetworkcookiejar.h b/src/network/access/qnetworkcookiejar.h
index d6370cbc05..f9c1549e20 100644
--- a/src/network/access/qnetworkcookiejar.h
+++ b/src/network/access/qnetworkcookiejar.h
@@ -40,6 +40,7 @@
#ifndef QNETWORKCOOKIEJAR_H
#define QNETWORKCOOKIEJAR_H
+#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/QObject>
#include <QtCore/QUrl>
diff --git a/src/network/access/qnetworkcookiejar_p.h b/src/network/access/qnetworkcookiejar_p.h
index b6f4eb2bc0..43f189a40c 100644
--- a/src/network/access/qnetworkcookiejar_p.h
+++ b/src/network/access/qnetworkcookiejar_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "private/qobject_p.h"
#include "qnetworkcookie.h"
diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp
index 077826ccf6..ce3b773c64 100644
--- a/src/network/access/qnetworkdiskcache.cpp
+++ b/src/network/access/qnetworkdiskcache.cpp
@@ -422,7 +422,7 @@ QIODevice *QNetworkDiskCache::data(const QUrl &url)
// ### verify that QFile uses the fd size and not the file name
qint64 size = file->size() - file->pos();
const uchar *p = 0;
-#if !defined(Q_OS_WINCE) && !defined(Q_OS_INTEGRITY)
+#if !defined(Q_OS_INTEGRITY)
p = file->map(file->pos(), size);
#endif
if (p) {
diff --git a/src/network/access/qnetworkdiskcache.h b/src/network/access/qnetworkdiskcache.h
index ad8b83a5f3..a3aa8d3a07 100644
--- a/src/network/access/qnetworkdiskcache.h
+++ b/src/network/access/qnetworkdiskcache.h
@@ -40,6 +40,7 @@
#ifndef QNETWORKDISKCACHE_H
#define QNETWORKDISKCACHE_H
+#include <QtNetwork/qtnetworkglobal.h>
#include <QtNetwork/qabstractnetworkcache.h>
QT_BEGIN_NAMESPACE
diff --git a/src/network/access/qnetworkdiskcache_p.h b/src/network/access/qnetworkdiskcache_p.h
index 9e035863dd..e47b93b09d 100644
--- a/src/network/access/qnetworkdiskcache_p.h
+++ b/src/network/access/qnetworkdiskcache_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "private/qabstractnetworkcache_p.h"
#include <qbuffer.h>
diff --git a/src/network/access/qnetworkfile.cpp b/src/network/access/qnetworkfile.cpp
new file mode 100644
index 0000000000..374dd26e2e
--- /dev/null
+++ b/src/network/access/qnetworkfile.cpp
@@ -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$
+**
+****************************************************************************/
+
+#include "qnetworkfile_p.h"
+
+#include <QtCore/QDebug>
+#include <QNetworkReply>
+#include <QtCore/QDateTime>
+#include <QtCore/QFileInfo>
+#include <QtCore/QMetaObject>
+#include <QtCore/QCoreApplication>
+
+QT_BEGIN_NAMESPACE
+
+QNetworkFile::QNetworkFile()
+ : QFile()
+{
+}
+
+QNetworkFile::QNetworkFile(const QString &name)
+ : QFile(name)
+{
+}
+
+void QNetworkFile::open()
+{
+ bool opened = false;
+ QFileInfo fi(fileName());
+ if (fi.isDir()) {
+ QString msg = QCoreApplication::translate("QNetworkAccessFileBackend",
+ "Cannot open %1: Path is a directory").arg(fileName());
+ error(QNetworkReply::ContentOperationNotPermittedError, msg);
+ } else {
+ headerRead(QNetworkRequest::LastModifiedHeader, QVariant::fromValue(fi.lastModified()));
+ headerRead(QNetworkRequest::ContentLengthHeader, QVariant::fromValue(fi.size()));
+ opened = QFile::open(QIODevice::ReadOnly | QIODevice::Unbuffered);
+ if (!opened) {
+ QString msg = QCoreApplication::translate("QNetworkAccessFileBackend",
+ "Error opening %1: %2").arg(fileName(), errorString());
+ if (exists())
+ error(QNetworkReply::ContentAccessDenied, msg);
+ else
+ error(QNetworkReply::ContentNotFoundError, msg);
+ }
+ }
+ finished(opened);
+}
+
+void QNetworkFile::close()
+{
+ // This override is needed because 'using' keyword cannot be used for slots. And the base
+ // function is not an invokable/slot function.
+ QFile::close();
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qnetworkfile_p.h b/src/network/access/qnetworkfile_p.h
new file mode 100644
index 0000000000..dd56b24bd8
--- /dev/null
+++ b/src/network/access/qnetworkfile_p.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** 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 QNETWORKFILE_H
+#define QNETWORKFILE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtNetwork/private/qtnetworkglobal_p.h>
+#include <QFile>
+#include <qnetworkreply.h>
+
+QT_BEGIN_NAMESPACE
+
+class QNetworkFile : public QFile
+{
+ Q_OBJECT
+public:
+ QNetworkFile();
+ QNetworkFile(const QString &name);
+ using QFile::open;
+
+public Q_SLOTS:
+ void open();
+ void close() Q_DECL_OVERRIDE;
+
+Q_SIGNALS:
+ void finished(bool ok);
+ void headerRead(QNetworkRequest::KnownHeaders header, const QVariant &value);
+ void error(QNetworkReply::NetworkError error, const QString &message);
+};
+
+QT_END_NAMESPACE
+
+#endif // QNETWORKFILE_H
diff --git a/src/network/access/qnetworkreply.h b/src/network/access/qnetworkreply.h
index 9aaf06a803..1419db8597 100644
--- a/src/network/access/qnetworkreply.h
+++ b/src/network/access/qnetworkreply.h
@@ -40,6 +40,7 @@
#ifndef QNETWORKREPLY_H
#define QNETWORKREPLY_H
+#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/QIODevice>
#include <QtCore/QString>
#include <QtCore/QVariant>
diff --git a/src/network/access/qnetworkreply_p.h b/src/network/access/qnetworkreply_p.h
index 420b862b17..66d8c9d527 100644
--- a/src/network/access/qnetworkreply_p.h
+++ b/src/network/access/qnetworkreply_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkrequest.h"
#include "qnetworkrequest_p.h"
#include "qnetworkreply.h"
diff --git a/src/network/access/qnetworkreplydataimpl_p.h b/src/network/access/qnetworkreplydataimpl_p.h
index 60d3f583cc..c8b44d7539 100644
--- a/src/network/access/qnetworkreplydataimpl_p.h
+++ b/src/network/access/qnetworkreplydataimpl_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkreply.h"
#include "qnetworkreply_p.h"
#include "qnetworkaccessmanager.h"
diff --git a/src/network/access/qnetworkreplyfileimpl.cpp b/src/network/access/qnetworkreplyfileimpl.cpp
index 36bc4b41df..ef319ebf0d 100644
--- a/src/network/access/qnetworkreplyfileimpl.cpp
+++ b/src/network/access/qnetworkreplyfileimpl.cpp
@@ -40,31 +40,45 @@
#include "qnetworkreplyfileimpl_p.h"
#include "QtCore/qdatetime.h"
+#include "qnetworkaccessmanager_p.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QFileInfo>
+#include <QtCore/QThread>
+#include "qnetworkfile_p.h"
+#include "qnetworkrequest.h"
QT_BEGIN_NAMESPACE
QNetworkReplyFileImplPrivate::QNetworkReplyFileImplPrivate()
- : QNetworkReplyPrivate(), realFileSize(0)
+ : QNetworkReplyPrivate(), managerPrivate(0), realFile(0)
{
+ qRegisterMetaType<QNetworkRequest::KnownHeaders>();
+ qRegisterMetaType<QNetworkReply::NetworkError>();
}
QNetworkReplyFileImpl::~QNetworkReplyFileImpl()
{
+ QNetworkReplyFileImplPrivate *d = (QNetworkReplyFileImplPrivate*) d_func();
+ if (d->realFile) {
+ if (d->realFile->thread() == QThread::currentThread())
+ delete d->realFile;
+ else
+ QMetaObject::invokeMethod(d->realFile, "deleteLater", Qt::QueuedConnection);
+ }
}
-QNetworkReplyFileImpl::QNetworkReplyFileImpl(QObject *parent, const QNetworkRequest &req, const QNetworkAccessManager::Operation op)
- : QNetworkReply(*new QNetworkReplyFileImplPrivate(), parent)
+QNetworkReplyFileImpl::QNetworkReplyFileImpl(QNetworkAccessManager *manager, const QNetworkRequest &req, const QNetworkAccessManager::Operation op)
+ : QNetworkReply(*new QNetworkReplyFileImplPrivate(), manager)
{
setRequest(req);
setUrl(req.url());
setOperation(op);
- setFinished(true);
QNetworkReply::open(QIODevice::ReadOnly);
QNetworkReplyFileImplPrivate *d = (QNetworkReplyFileImplPrivate*) d_func();
+ d->managerPrivate = manager->d_func();
+
QUrl url = req.url();
if (url.host() == QLatin1String("localhost"))
url.setHost(QString());
@@ -77,7 +91,7 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QObject *parent, const QNetworkRequ
setError(QNetworkReply::ProtocolInvalidOperationError, msg);
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProtocolInvalidOperationError));
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+ fileOpenFinished(false);
return;
}
#endif
@@ -85,7 +99,6 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QObject *parent, const QNetworkRequ
url.setPath(QLatin1String("/"));
setUrl(url);
-
QString fileName = url.toLocalFile();
if (fileName.isEmpty()) {
const QString scheme = url.scheme();
@@ -101,68 +114,85 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QObject *parent, const QNetworkRequ
}
}
- QFileInfo fi(fileName);
- if (fi.isDir()) {
- QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Cannot open %1: Path is a directory").arg(url.toString());
- setError(QNetworkReply::ContentOperationNotPermittedError, msg);
- QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
- Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentOperationNotPermittedError));
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
- return;
- }
-
- d->realFile.setFileName(fileName);
- bool opened = d->realFile.open(QIODevice::ReadOnly | QIODevice::Unbuffered);
-
- // could we open the file?
- if (!opened) {
- QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Error opening %1: %2")
- .arg(d->realFile.fileName(), d->realFile.errorString());
-
- if (d->realFile.exists()) {
- setError(QNetworkReply::ContentAccessDenied, msg);
- QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
- Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentAccessDenied));
- } else {
- setError(QNetworkReply::ContentNotFoundError, msg);
+ if (req.attribute(QNetworkRequest::BackgroundRequestAttribute).toBool()) { // Asynchronous open
+ auto realFile = new QNetworkFile(fileName);
+ connect(realFile, &QNetworkFile::headerRead, this, &QNetworkReplyFileImpl::setHeader,
+ Qt::QueuedConnection);
+ connect(realFile, &QNetworkFile::error, this, &QNetworkReplyFileImpl::setError,
+ Qt::QueuedConnection);
+ connect(realFile, SIGNAL(finished(bool)), SLOT(fileOpenFinished(bool)),
+ Qt::QueuedConnection);
+
+ realFile->moveToThread(d->managerPrivate->createThread());
+ QMetaObject::invokeMethod(realFile, "open", Qt::QueuedConnection);
+
+ d->realFile = realFile;
+ } else { // Synch open
+ setFinished(true);
+
+ QFileInfo fi(fileName);
+ if (fi.isDir()) {
+ QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Cannot open %1: Path is a directory").arg(url.toString());
+ setError(QNetworkReply::ContentOperationNotPermittedError, msg);
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
- Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentNotFoundError));
+ Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentOperationNotPermittedError));
+ QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+ return;
+ }
+ d->realFile = new QFile(fileName, this);
+ bool opened = d->realFile->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
+
+ // could we open the file?
+ if (!opened) {
+ QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Error opening %1: %2")
+ .arg(d->realFile->fileName(), d->realFile->errorString());
+
+ if (fi.exists()) {
+ setError(QNetworkReply::ContentAccessDenied, msg);
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentAccessDenied));
+ } else {
+ setError(QNetworkReply::ContentNotFoundError, msg);
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentNotFoundError));
+ }
+ QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+ return;
}
+ setHeader(QNetworkRequest::LastModifiedHeader, fi.lastModified());
+ setHeader(QNetworkRequest::ContentLengthHeader, fi.size());
+
+ QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection,
+ Q_ARG(qint64, fi.size()), Q_ARG(qint64, fi.size()));
+ QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection);
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
- return;
}
-
- setHeader(QNetworkRequest::LastModifiedHeader, fi.lastModified());
- d->realFileSize = fi.size();
- setHeader(QNetworkRequest::ContentLengthHeader, d->realFileSize);
-
- QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
- QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection,
- Q_ARG(qint64, d->realFileSize), Q_ARG(qint64, d->realFileSize));
- QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection);
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
}
void QNetworkReplyFileImpl::close()
{
Q_D(QNetworkReplyFileImpl);
QNetworkReply::close();
- d->realFile.close();
+ if (d->realFile) {
+ if (d->realFile->thread() == thread())
+ d->realFile->close();
+ else
+ QMetaObject::invokeMethod(d->realFile, "close", Qt::QueuedConnection);
+ }
}
void QNetworkReplyFileImpl::abort()
{
- Q_D(QNetworkReplyFileImpl);
- QNetworkReply::close();
- d->realFile.close();
+ close();
}
qint64 QNetworkReplyFileImpl::bytesAvailable() const
{
Q_D(const QNetworkReplyFileImpl);
- if (!d->realFile.isOpen())
+ if (!d->isFinished || !d->realFile || !d->realFile->isOpen())
return QNetworkReply::bytesAvailable();
- return QNetworkReply::bytesAvailable() + d->realFile.bytesAvailable();
+ return QNetworkReply::bytesAvailable() + d->realFile->bytesAvailable();
}
bool QNetworkReplyFileImpl::isSequential () const
@@ -172,8 +202,9 @@ bool QNetworkReplyFileImpl::isSequential () const
qint64 QNetworkReplyFileImpl::size() const
{
- Q_D(const QNetworkReplyFileImpl);
- return d->realFileSize;
+ bool ok;
+ int size = header(QNetworkRequest::ContentLengthHeader).toInt(&ok);
+ return ok ? size : 0;
}
/*!
@@ -182,11 +213,11 @@ qint64 QNetworkReplyFileImpl::size() const
qint64 QNetworkReplyFileImpl::readData(char *data, qint64 maxlen)
{
Q_D(QNetworkReplyFileImpl);
- if (!d->realFile.isOpen())
+ if (!d->isFinished || !d->realFile || !d->realFile->isOpen())
return -1;
- qint64 ret = d->realFile.read(data, maxlen);
- if (bytesAvailable() == 0 && d->realFile.isOpen())
- d->realFile.close();
+ qint64 ret = d->realFile->read(data, maxlen);
+ if (bytesAvailable() == 0)
+ d->realFile->close();
if (ret == 0 && bytesAvailable() == 0)
return -1;
else {
@@ -196,6 +227,17 @@ qint64 QNetworkReplyFileImpl::readData(char *data, qint64 maxlen)
}
}
+void QNetworkReplyFileImpl::fileOpenFinished(bool isOpen)
+{
+ setFinished(true);
+ if (isOpen) {
+ const auto fileSize = size();
+ Q_EMIT metaDataChanged();
+ Q_EMIT downloadProgress(fileSize, fileSize);
+ Q_EMIT readyRead();
+ }
+ Q_EMIT finished();
+}
QT_END_NAMESPACE
diff --git a/src/network/access/qnetworkreplyfileimpl_p.h b/src/network/access/qnetworkreplyfileimpl_p.h
index bac00881a8..d6af66152e 100644
--- a/src/network/access/qnetworkreplyfileimpl_p.h
+++ b/src/network/access/qnetworkreplyfileimpl_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkreply.h"
#include "qnetworkreply_p.h"
#include "qnetworkaccessmanager.h"
@@ -59,13 +60,12 @@
QT_BEGIN_NAMESPACE
-
class QNetworkReplyFileImplPrivate;
class QNetworkReplyFileImpl: public QNetworkReply
{
Q_OBJECT
public:
- QNetworkReplyFileImpl(QObject *parent, const QNetworkRequest &req, const QNetworkAccessManager::Operation op);
+ QNetworkReplyFileImpl(QNetworkAccessManager *manager, const QNetworkRequest &req, const QNetworkAccessManager::Operation op);
~QNetworkReplyFileImpl();
virtual void abort() Q_DECL_OVERRIDE;
@@ -77,6 +77,9 @@ public:
virtual qint64 readData(char *data, qint64 maxlen) Q_DECL_OVERRIDE;
+private Q_SLOTS:
+ void fileOpenFinished(bool isOpen);
+
Q_DECLARE_PRIVATE(QNetworkReplyFileImpl)
};
@@ -85,12 +88,14 @@ class QNetworkReplyFileImplPrivate: public QNetworkReplyPrivate
public:
QNetworkReplyFileImplPrivate();
- QFile realFile;
- qint64 realFileSize;
+ QNetworkAccessManagerPrivate *managerPrivate;
+ QPointer<QFile> realFile;
Q_DECLARE_PUBLIC(QNetworkReplyFileImpl)
};
QT_END_NAMESPACE
+Q_DECLARE_METATYPE(QNetworkRequest::KnownHeaders)
+
#endif // QNETWORKREPLYFILEIMPL_H
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index 6bf3c56db2..b5e44fa29a 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -522,36 +522,36 @@ bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &h
* now
* is the current (local) time
*/
- int age_value = 0;
+ qint64 age_value = 0;
it = cacheHeaders.findRawHeader("age");
if (it != cacheHeaders.rawHeaders.constEnd())
- age_value = it->second.toInt();
+ age_value = it->second.toLongLong();
QDateTime dateHeader;
- int date_value = 0;
+ qint64 date_value = 0;
it = cacheHeaders.findRawHeader("date");
if (it != cacheHeaders.rawHeaders.constEnd()) {
dateHeader = QNetworkHeadersPrivate::fromHttpDate(it->second);
- date_value = dateHeader.toTime_t();
+ date_value = dateHeader.toSecsSinceEpoch();
}
- int now = currentDateTime.toTime_t();
- int request_time = now;
- int response_time = now;
+ qint64 now = currentDateTime.toSecsSinceEpoch();
+ qint64 request_time = now;
+ qint64 response_time = now;
// Algorithm from RFC 2616 section 13.2.3
- int apparent_age = qMax(0, response_time - date_value);
- int corrected_received_age = qMax(apparent_age, age_value);
- int response_delay = response_time - request_time;
- int corrected_initial_age = corrected_received_age + response_delay;
- int resident_time = now - response_time;
- int current_age = corrected_initial_age + resident_time;
+ qint64 apparent_age = qMax<qint64>(0, response_time - date_value);
+ qint64 corrected_received_age = qMax(apparent_age, age_value);
+ qint64 response_delay = response_time - request_time;
+ qint64 corrected_initial_age = corrected_received_age + response_delay;
+ qint64 resident_time = now - response_time;
+ qint64 current_age = corrected_initial_age + resident_time;
- int freshness_lifetime = 0;
+ qint64 freshness_lifetime = 0;
// RFC 2616 13.2.4 Expiration Calculations
if (lastModified.isValid() && dateHeader.isValid()) {
- int diff = lastModified.secsTo(dateHeader);
+ qint64 diff = lastModified.secsTo(dateHeader);
freshness_lifetime = diff / 10;
if (httpRequest.headerField("Warning").isEmpty()) {
QDateTime dt = currentDateTime.addSecs(current_age);
@@ -601,17 +601,10 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
thread->setObjectName(QStringLiteral("Qt HTTP synchronous thread"));
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
- } else if (!managerPrivate->httpThread) {
+ } else {
// We use the manager-global thread.
// At some point we could switch to having multiple threads if it makes sense.
- managerPrivate->httpThread = new QThread();
- managerPrivate->httpThread->setObjectName(QStringLiteral("Qt HTTP thread"));
- managerPrivate->httpThread->start();
-
- thread = managerPrivate->httpThread;
- } else {
- // Asynchronous request, thread already exists
- thread = managerPrivate->httpThread;
+ thread = managerPrivate->createThread();
}
QUrl url = newHttpRequest.url();
@@ -745,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)
@@ -1123,8 +1119,8 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
cookedHeaders.clear();
- if (managerPrivate->httpThread)
- managerPrivate->httpThread->disconnect();
+ if (managerPrivate->thread)
+ managerPrivate->thread->disconnect();
// Recurse
QMetaObject::invokeMethod(q, "start", Qt::QueuedConnection,
diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h
index 4aba915c7d..868fa617b6 100644
--- a/src/network/access/qnetworkreplyhttpimpl_p.h
+++ b/src/network/access/qnetworkreplyhttpimpl_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkrequest.h"
#include "qnetworkreply.h"
diff --git a/src/network/access/qnetworkreplyimpl_p.h b/src/network/access/qnetworkreplyimpl_p.h
index 054cbcc3a7..7cd99392d3 100644
--- a/src/network/access/qnetworkreplyimpl_p.h
+++ b/src/network/access/qnetworkreplyimpl_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkreply.h"
#include "qnetworkreply_p.h"
#include "qnetworkaccessmanager.h"
diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h
index 4a90a50e53..00375f285c 100644
--- a/src/network/access/qnetworkrequest.h
+++ b/src/network/access/qnetworkrequest.h
@@ -40,6 +40,7 @@
#ifndef QNETWORKREQUEST_H
#define QNETWORKREQUEST_H
+#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/QSharedDataPointer>
#include <QtCore/QString>
#include <QtCore/QUrl>
@@ -86,6 +87,8 @@ public:
BackgroundRequestAttribute,
SpdyAllowedAttribute,
SpdyWasUsedAttribute,
+ HTTP2AllowedAttribute,
+ HTTP2WasUsedAttribute,
EmitAllUploadProgressSignalsAttribute,
FollowRedirectsAttribute,
diff --git a/src/network/access/qnetworkrequest_p.h b/src/network/access/qnetworkrequest_p.h
index de6941c476..5e18da6d55 100644
--- a/src/network/access/qnetworkrequest_p.h
+++ b/src/network/access/qnetworkrequest_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qnetworkrequest.h"
#include "QtCore/qbytearray.h"
#include "QtCore/qlist.h"
diff --git a/src/network/access/qspdyprotocolhandler.cpp b/src/network/access/qspdyprotocolhandler.cpp
index 413b03cc22..445a2a1c29 100644
--- a/src/network/access/qspdyprotocolhandler.cpp
+++ b/src/network/access/qspdyprotocolhandler.cpp
@@ -458,7 +458,7 @@ bool QSpdyProtocolHandler::uncompressHeader(const QByteArray &input, QByteArray
break;
}
default: {
- qWarning() << "got unexpected zlib return value:" << zlibRet;
+ qWarning("got unexpected zlib return value: %d", zlibRet);
return false;
}
}
@@ -849,7 +849,7 @@ void QSpdyProtocolHandler::handleControlFrame(const QByteArray &frameHeaders) //
break;
}
default:
- qWarning() << "cannot handle frame of type" << type;
+ qWarning("cannot handle frame of type %d", int(type));
}
}
@@ -1073,7 +1073,7 @@ void QSpdyProtocolHandler::handleSETTINGS(char flags, quint32 /*length*/, const
break;
}
default:
- qWarning() << "found unknown settings value" << value;
+ qWarning("found unknown settings value %u", uint(value));
}
}
}
@@ -1112,7 +1112,7 @@ void QSpdyProtocolHandler::handleGOAWAY(char /*flags*/, quint32 /*length*/,
break;
}
default:
- qWarning() << "unexpected status code" << statusCode;
+ qWarning("unexpected status code %d", int(statusCode));
errorCode = QNetworkReply::ProtocolUnknownError;
}
diff --git a/src/network/access/qspdyprotocolhandler_p.h b/src/network/access/qspdyprotocolhandler_p.h
index aba081b9d1..0a18505b23 100644
--- a/src/network/access/qspdyprotocolhandler_p.h
+++ b/src/network/access/qspdyprotocolhandler_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include <QtNetwork/private/qtnetworkglobal_p.h>
#include <private/qabstractprotocolhandler_p.h>
#include <QtNetwork/qnetworkreply.h>
#include <private/qbytedata_p.h>