diff options
Diffstat (limited to 'src/network/access/http2')
-rw-r--r-- | src/network/access/http2/hpack.cpp | 49 | ||||
-rw-r--r-- | src/network/access/http2/hpack_p.h | 3 | ||||
-rw-r--r-- | src/network/access/http2/hpacktable.cpp | 9 | ||||
-rw-r--r-- | src/network/access/http2/http2frames.cpp | 13 | ||||
-rw-r--r-- | src/network/access/http2/http2frames_p.h | 19 | ||||
-rw-r--r-- | src/network/access/http2/http2protocol.cpp | 50 | ||||
-rw-r--r-- | src/network/access/http2/http2protocol_p.h | 8 |
7 files changed, 116 insertions, 35 deletions
diff --git a/src/network/access/http2/hpack.cpp b/src/network/access/http2/hpack.cpp index 58af04bbb5..9e970dda53 100644 --- a/src/network/access/http2/hpack.cpp +++ b/src/network/access/http2/hpack.cpp @@ -91,7 +91,7 @@ bool read_bit_pattern(const BitPattern &pattern, BitIStream &inputStream) return true; } -bool is_request_pseudo_header(const QByteArray &name) +bool is_request_pseudo_header(QByteArrayView name) { return name == ":method" || name == ":scheme" || name == ":authority" || name == ":path"; @@ -194,8 +194,8 @@ bool Encoder::encodeRequestPseudoHeaders(BitOStream &outputStream, using size_type = decltype(header.size()); bool methodFound = false; - const char *headerName[] = {":authority", ":scheme", ":path"}; - const size_type nHeaders = sizeof headerName / sizeof headerName[0]; + constexpr QByteArrayView headerName[] = {":authority", ":scheme", ":path"}; + constexpr size_type nHeaders = std::size(headerName); bool headerFound[nHeaders] = {}; for (const auto &field : header) { @@ -504,6 +504,49 @@ void Decoder::handleStreamError(BitIStream &inputStream) // HTTP2 layer will end with session error/COMPRESSION_ERROR. } +std::optional<QUrl> makePromiseKeyUrl(const HttpHeader &requestHeader) +{ + constexpr QByteArrayView names[] = { ":authority", ":method", ":path", ":scheme" }; + enum PseudoHeaderEnum + { + Authority, + Method, + Path, + Scheme + }; + std::array<std::optional<QByteArrayView>, std::size(names)> pseudoHeaders{}; + for (const auto &field : requestHeader) { + const auto *it = std::find(std::begin(names), std::end(names), QByteArrayView(field.name)); + if (it != std::end(names)) { + const auto index = std::distance(std::begin(names), it); + if (field.value.isEmpty() || pseudoHeaders.at(index).has_value()) + return {}; + pseudoHeaders[index] = field.value; + } + } + + auto optionalIsSet = [](const auto &x) { return x.has_value(); }; + if (!std::all_of(pseudoHeaders.begin(), pseudoHeaders.end(), optionalIsSet)) { + // All four required, HTTP/2 8.1.2.3. + return {}; + } + + const QByteArrayView method = pseudoHeaders[Method].value(); + if (method.compare("get", Qt::CaseInsensitive) != 0 && + method.compare("head", Qt::CaseInsensitive) != 0) { + return {}; + } + + QUrl url; + url.setScheme(QLatin1StringView(pseudoHeaders[Scheme].value())); + url.setAuthority(QLatin1StringView(pseudoHeaders[Authority].value())); + url.setPath(QLatin1StringView(pseudoHeaders[Path].value())); + + if (!url.isValid()) + return {}; + return url; +} + } QT_END_NAMESPACE diff --git a/src/network/access/http2/hpack_p.h b/src/network/access/http2/hpack_p.h index 75693da73c..b407b81941 100644 --- a/src/network/access/http2/hpack_p.h +++ b/src/network/access/http2/hpack_p.h @@ -18,8 +18,10 @@ #include "hpacktable_p.h" #include <QtCore/qglobal.h> +#include <QtCore/qurl.h> #include <vector> +#include <optional> QT_BEGIN_NAMESPACE @@ -112,6 +114,7 @@ private: FieldLookupTable lookupTable; }; +std::optional<QUrl> makePromiseKeyUrl(const HttpHeader &requestHeader); } QT_END_NAMESPACE diff --git a/src/network/access/http2/hpacktable.cpp b/src/network/access/http2/hpacktable.cpp index 0b69ee86a9..2c728b37e3 100644 --- a/src/network/access/http2/hpacktable.cpp +++ b/src/network/access/http2/hpacktable.cpp @@ -26,8 +26,10 @@ HeaderSize entry_size(QByteArrayView name, QByteArrayView value) // 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) + size_t sum; + if (qAddOverflow(size_t(name.size()), size_t(value.size()), &sum)) + return HeaderSize(); + if (sum > (std::numeric_limits<unsigned>::max() - 32)) return HeaderSize(); return HeaderSize(true, quint32(sum + 32)); } @@ -346,8 +348,7 @@ quint32 FieldLookupTable::indexOfChunk(const Chunk *chunk) const return quint32(i); } - Q_UNREACHABLE(); - return 0; + Q_UNREACHABLE_RETURN(0); } quint32 FieldLookupTable::keyToIndex(const SearchEntry &key) const diff --git a/src/network/access/http2/http2frames.cpp b/src/network/access/http2/http2frames.cpp index 0c70c98ef8..e07c96b803 100644 --- a/src/network/access/http2/http2frames.cpp +++ b/src/network/access/http2/http2frames.cpp @@ -135,6 +135,7 @@ FrameStatus Frame::validateHeader() const // 6.6 PUSH_PROMISE if (framePayloadSize < 4) return FrameStatus::sizeError; + break; default: // DATA/HEADERS/CONTINUATION will be verified // when we have payload. @@ -258,7 +259,7 @@ const uchar *Frame::hpackBlockBegin() const return begin; } -FrameStatus FrameReader::read(QAbstractSocket &socket) +FrameStatus FrameReader::read(QIODevice &socket) { if (offset < frameHeaderSize) { if (!readHeader(socket)) @@ -286,7 +287,7 @@ FrameStatus FrameReader::read(QAbstractSocket &socket) return frame.validatePayload(); } -bool FrameReader::readHeader(QAbstractSocket &socket) +bool FrameReader::readHeader(QIODevice &socket) { Q_ASSERT(offset < frameHeaderSize); @@ -302,7 +303,7 @@ bool FrameReader::readHeader(QAbstractSocket &socket) return offset == frameHeaderSize; } -bool FrameReader::readPayload(QAbstractSocket &socket) +bool FrameReader::readPayload(QIODevice &socket) { Q_ASSERT(offset < frame.buffer.size()); Q_ASSERT(frame.buffer.size() > frameHeaderSize); @@ -393,7 +394,7 @@ void FrameWriter::updatePayloadSize() setPayloadSize(size); } -bool FrameWriter::write(QAbstractSocket &socket) const +bool FrameWriter::write(QIODevice &socket) const { auto &buffer = frame.buffer; Q_ASSERT(buffer.size() >= frameHeaderSize); @@ -407,7 +408,7 @@ bool FrameWriter::write(QAbstractSocket &socket) const return nWritten != -1 && size_type(nWritten) == buffer.size(); } -bool FrameWriter::writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit) +bool FrameWriter::writeHEADERS(QIODevice &socket, quint32 sizeLimit) { auto &buffer = frame.buffer; Q_ASSERT(buffer.size() >= frameHeaderSize); @@ -457,7 +458,7 @@ bool FrameWriter::writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit) return true; } -bool FrameWriter::writeDATA(QAbstractSocket &socket, quint32 sizeLimit, +bool FrameWriter::writeDATA(QIODevice &socket, quint32 sizeLimit, const uchar *src, quint32 size) { // With DATA frame(s) we always have: diff --git a/src/network/access/http2/http2frames_p.h b/src/network/access/http2/http2frames_p.h index be87f43fbe..48e3f751b7 100644 --- a/src/network/access/http2/http2frames_p.h +++ b/src/network/access/http2/http2frames_p.h @@ -27,7 +27,7 @@ QT_BEGIN_NAMESPACE class QHttp2ProtocolHandler; -class QAbstractSocket; +class QIODevice; namespace Http2 { @@ -65,15 +65,15 @@ struct Q_AUTOTEST_EXPORT Frame class Q_AUTOTEST_EXPORT FrameReader { public: - FrameStatus read(QAbstractSocket &socket); + FrameStatus read(QIODevice &socket); Frame &inboundFrame() { return frame; } private: - bool readHeader(QAbstractSocket &socket); - bool readPayload(QAbstractSocket &socket); + bool readHeader(QIODevice &socket); + bool readPayload(QIODevice &socket); quint32 offset = 0; Frame frame; @@ -123,20 +123,25 @@ public: { append(&payload[0], &payload[0] + payload.size()); } + void append(QByteArrayView payload) + { + append(reinterpret_cast<const uchar *>(payload.begin()), + reinterpret_cast<const uchar *>(payload.end())); + } void append(const uchar *begin, const uchar *end); // Write as a single frame: - bool write(QAbstractSocket &socket) const; + bool write(QIODevice &socket) const; // Two types of frames we are sending are affected by frame size limits: // HEADERS and DATA. HEADERS' payload (hpacked HTTP headers, following a // frame header) is always in our 'buffer', we send the initial HEADERS // frame first and then CONTINUTATION frame(s) if needed: - bool writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit); + bool writeHEADERS(QIODevice &socket, quint32 sizeLimit); // With DATA frames the actual payload is never in our 'buffer', it's a // 'readPointer' from QNonContiguousData. We split this payload as needed // into DATA frames with correct payload size fitting into frame size limit: - bool writeDATA(QAbstractSocket &socket, quint32 sizeLimit, + bool writeDATA(QIODevice &socket, quint32 sizeLimit, const uchar *src, quint32 size); private: void updatePayloadSize(); diff --git a/src/network/access/http2/http2protocol.cpp b/src/network/access/http2/http2protocol.cpp index 966f294e81..8e7e176c41 100644 --- a/src/network/access/http2/http2protocol.cpp +++ b/src/network/access/http2/http2protocol.cpp @@ -76,12 +76,10 @@ void appendProtocolUpgradeHeaders(const QHttp2Configuration &config, QHttpNetwor Q_ASSERT(request); // RFC 2616, 14.10 // RFC 7540, 3.2 - QByteArray value(request->headerField("Connection")); + const QByteArray connectionHeader = request->headerField("Connection"); + const auto separator = connectionHeader.isEmpty() ? QByteArrayView() : QByteArrayView(", "); // We _append_ 'Upgrade': - if (value.size()) - value += ", "; - - value += "Upgrade, HTTP2-Settings"; + QByteArray value = connectionHeader + separator + "Upgrade, HTTP2-Settings"; request->setHeaderField("Connection", value); // This we just (re)write. request->setHeaderField("Upgrade", "h2c"); @@ -186,19 +184,45 @@ QNetworkReply::NetworkError qt_error(quint32 errorCode) bool is_protocol_upgraded(const QHttpNetworkReply &reply) { - if (reply.statusCode() == 101) { - // Do some minimal checks here - we expect 'Upgrade: h2c' to be found. - const auto &header = reply.header(); - for (const QPair<QByteArray, QByteArray> &field : header) { - if (field.first.compare("upgrade", Qt::CaseInsensitive) == 0 && - field.second.compare("h2c", Qt::CaseInsensitive) == 0) - return true; - } + if (reply.statusCode() != 101) + return false; + + // Do some minimal checks here - we expect 'Upgrade: h2c' to be found. + for (const auto &v : reply.header().values(QHttpHeaders::WellKnownHeader::Upgrade)) { + if (v.compare("h2c", Qt::CaseInsensitive) == 0) + return true; } return false; } +std::vector<uchar> assemble_hpack_block(const std::vector<Frame> &frames) +{ + std::vector<uchar> hpackBlock; + + size_t total = 0; + for (const auto &frame : frames) { + if (qAddOverflow(total, size_t{frame.hpackBlockSize()}, &total)) + return hpackBlock; + } + + if (!total) + return hpackBlock; + + hpackBlock.resize(total); + auto dst = hpackBlock.begin(); + for (const auto &frame : frames) { + if (const auto hpackBlockSize = frame.hpackBlockSize()) { + const uchar *src = frame.hpackBlockBegin(); + std::copy(src, src + hpackBlockSize, dst); + dst += hpackBlockSize; + } + } + + return hpackBlock; +} + + } // namespace Http2 QT_END_NAMESPACE diff --git a/src/network/access/http2/http2protocol_p.h b/src/network/access/http2/http2protocol_p.h index d19208895a..f0f18d1dd5 100644 --- a/src/network/access/http2/http2protocol_p.h +++ b/src/network/access/http2/http2protocol_p.h @@ -21,6 +21,8 @@ #include <QtCore/private/qglobal_p.h> #include <QtCore/qmap.h> +#include <vector> + // Different HTTP/2 constants/values as defined by RFC 7540. QT_BEGIN_NAMESPACE @@ -112,11 +114,13 @@ const quint32 lastValidStreamID((quint32(1) << 31) - 1); // HTTP/2, 5.1.1 // HTTP/2 servers are not afraid to immediately set it to the possible max, // we do the same and split this window size between our concurrent streams. const qint32 maxSessionReceiveWindowSize((quint32(1) << 31) - 1); -const qint32 qtDefaultStreamReceiveWindowSize = maxSessionReceiveWindowSize / maxConcurrentStreams; +// Presumably, we never use up to 100 streams so let it be 10 simultaneous: +const qint32 qtDefaultStreamReceiveWindowSize = maxSessionReceiveWindowSize / 10; -struct Frame configurationToSettingsFrame(const QHttp2Configuration &configuration); +struct Frame Q_AUTOTEST_EXPORT configurationToSettingsFrame(const QHttp2Configuration &configuration); QByteArray settingsFrameToBase64(const Frame &settingsFrame); void appendProtocolUpgradeHeaders(const QHttp2Configuration &configuration, QHttpNetworkRequest *request); +std::vector<uchar> assemble_hpack_block(const std::vector<Frame> &frames); extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength]; |