1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qhttpnetworkconnection_p.h"
41#include "qhttp2protocolhandler_p.h"
42
43#include "http2/http2frames_p.h"
44#include "http2/bitstreams_p.h"
45
46#include <private/qnoncontiguousbytedevice_p.h>
47
48#include <QtNetwork/qabstractsocket.h>
49#include <QtCore/qloggingcategory.h>
50#include <QtCore/qendian.h>
51#include <QtCore/qdebug.h>
52#include <QtCore/qlist.h>
53#include <QtCore/qurl.h>
54
55#include <qhttp2configuration.h>
56
57#ifndef QT_NO_NETWORKPROXY
58#include <QtNetwork/qnetworkproxy.h>
59#endif
60
61#include <qcoreapplication.h>
62
63#include <algorithm>
64#include <vector>
65
66QT_BEGIN_NAMESPACE
67
68namespace
69{
70
71HPack::HttpHeader build_headers(const QHttpNetworkRequest &request, quint32 maxHeaderListSize,
72 bool useProxy)
73{
74 using namespace HPack;
75
76 HttpHeader header;
77 header.reserve(300);
78
79 // 1. Before anything - mandatory fields, if they do not fit into maxHeaderList -
80 // then stop immediately with error.
81 const auto auth = request.url().authority(QUrl::FullyEncoded | QUrl::RemoveUserInfo).toLatin1();
82 header.push_back(HeaderField(":authority", auth));
83 header.push_back(HeaderField(":method", request.methodName()));
84 header.push_back(HeaderField(":path", request.uri(useProxy)));
85 header.push_back(HeaderField(":scheme", request.url().scheme().toLatin1()));
86
87 HeaderSize size = header_size(header);
88 if (!size.first) // Ooops!
89 return HttpHeader();
90
91 if (size.second > maxHeaderListSize)
92 return HttpHeader(); // Bad, we cannot send this request ...
93
94 const auto requestHeader = request.header();
95 for (const auto &field : requestHeader) {
96 const HeaderSize delta = entry_size(field.first, field.second);
97 if (!delta.first) // Overflow???
98 break;
99 if (std::numeric_limits<quint32>::max() - delta.second < size.second)
100 break;
101 size.second += delta.second;
102 if (size.second > maxHeaderListSize)
103 break;
104
105 if (field.first.compare("connection", Qt::CaseInsensitive) == 0 ||
106 field.first.compare("host", Qt::CaseInsensitive) == 0 ||
107 field.first.compare("keep-alive", Qt::CaseInsensitive) == 0 ||
108 field.first.compare("proxy-connection", Qt::CaseInsensitive) == 0 ||
109 field.first.compare("transfer-encoding", Qt::CaseInsensitive) == 0)
110 continue; // Those headers are not valid (section 3.2.1) - from QSpdyProtocolHandler
111 // TODO: verify with specs, which fields are valid to send ....
112 // toLower - 8.1.2 .... "header field names MUST be converted to lowercase prior
113 // to their encoding in HTTP/2.
114 // A request or response containing uppercase header field names
115 // MUST be treated as malformed (Section 8.1.2.6)".
116 header.push_back(HeaderField(field.first.toLower(), field.second));
117 }
118
119 return header;
120}
121
122std::vector<uchar> assemble_hpack_block(const std::vector<Http2::Frame> &frames)
123{
124 std::vector<uchar> hpackBlock;
125
126 quint32 total = 0;
127 for (const auto &frame : frames)
128 total += frame.hpackBlockSize();
129
130 if (!total)
131 return hpackBlock;
132
133 hpackBlock.resize(total);
134 auto dst = hpackBlock.begin();
135 for (const auto &frame : frames) {
136 if (const auto hpackBlockSize = frame.hpackBlockSize()) {
137 const uchar *src = frame.hpackBlockBegin();
138 std::copy(src, src + hpackBlockSize, dst);
139 dst += hpackBlockSize;
140 }
141 }
142
143 return hpackBlock;
144}
145
146QUrl urlkey_from_request(const QHttpNetworkRequest &request)
147{
148 QUrl url;
149
150 url.setScheme(request.url().scheme());
151 url.setAuthority(request.url().authority(QUrl::FullyEncoded | QUrl::RemoveUserInfo));
152 url.setPath(QLatin1String(request.uri(false)));
153
154 return url;
155}
156
157bool sum_will_overflow(qint32 windowSize, qint32 delta)
158{
159 if (windowSize > 0)
160 return std::numeric_limits<qint32>::max() - windowSize < delta;
161 return std::numeric_limits<qint32>::min() - windowSize > delta;
162}
163
164}// Unnamed namespace
165
166// Since we anyway end up having this in every function definition:
167using namespace Http2;
168
169const std::deque<quint32>::size_type QHttp2ProtocolHandler::maxRecycledStreams = 10000;
170const quint32 QHttp2ProtocolHandler::maxAcceptableTableSize;
171
172QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *channel)
173 : QAbstractProtocolHandler(channel),
174 decoder(HPack::FieldLookupTable::DefaultSize),
175 encoder(HPack::FieldLookupTable::DefaultSize, true)
176{
177 Q_ASSERT(channel && m_connection);
178 continuedFrames.reserve(20);
179
180 const auto h2Config = m_connection->http2Parameters();
181 maxSessionReceiveWindowSize = h2Config.sessionReceiveWindowSize();
182 pushPromiseEnabled = h2Config.serverPushEnabled();
183 streamInitialReceiveWindowSize = h2Config.streamReceiveWindowSize();
184 encoder.setCompressStrings(h2Config.huffmanCompressionEnabled());
185
186 if (!channel->ssl && m_connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
187 // We upgraded from HTTP/1.1 to HTTP/2. channel->request was already sent
188 // as HTTP/1.1 request. The response with status code 101 triggered
189 // protocol switch and now we are waiting for the real response, sent
190 // as HTTP/2 frames.
191 Q_ASSERT(channel->reply);
192 const quint32 initialStreamID = createNewStream(HttpMessagePair(channel->request, channel->reply),
193 true /* uploaded by HTTP/1.1 */);
194 Q_ASSERT(initialStreamID == 1);
195 Stream &stream = activeStreams[initialStreamID];
196 stream.state = Stream::halfClosedLocal;
197 }
198}
199
200void QHttp2ProtocolHandler::handleConnectionClosure()
201{
202 // The channel has just received RemoteHostClosedError and since it will
203 // not try (for HTTP/2) to re-connect, it's time to finish all replies
204 // with error.
205
206 // Maybe we still have some data to read and can successfully finish
207 // a stream/request?
208 _q_receiveReply();
209
210 // Finish all still active streams. If we previously had GOAWAY frame,
211 // we probably already closed some (or all) streams with ContentReSend
212 // error, but for those still active, not having any data to finish,
213 // we now report RemoteHostClosedError.
214 const auto errorString = QCoreApplication::translate("QHttp", "Connection closed");
215 for (auto it = activeStreams.begin(), eIt = activeStreams.end(); it != eIt; ++it)
216 finishStreamWithError(it.value(), QNetworkReply::RemoteHostClosedError, errorString);
217
218 // Make sure we'll never try to read anything later:
219 activeStreams.clear();
220 goingAway = true;
221}
222
223void QHttp2ProtocolHandler::ensureClientPrefaceSent()
224{
225 if (!prefaceSent)
226 sendClientPreface();
227}
228
229void QHttp2ProtocolHandler::_q_uploadDataReadyRead()
230{
231 if (!sender()) // QueuedConnection, firing after sender (byte device) was deleted.
232 return;
233
234 auto data = qobject_cast<QNonContiguousByteDevice *>(sender());
235 Q_ASSERT(data);
236 const qint32 streamID = streamIDs.value(data);
237 Q_ASSERT(streamID != 0);
238 Q_ASSERT(activeStreams.contains(streamID));
239 auto &stream = activeStreams[streamID];
240
241 if (!sendDATA(stream)) {
242 finishStreamWithError(stream, QNetworkReply::UnknownNetworkError,
243 QLatin1String("failed to send DATA"));
244 sendRST_STREAM(streamID, INTERNAL_ERROR);
245 markAsReset(streamID);
246 deleteActiveStream(streamID);
247 }
248}
249
250void QHttp2ProtocolHandler::_q_replyDestroyed(QObject *reply)
251{
252 const quint32 streamID = streamIDs.take(reply);
253 if (activeStreams.contains(streamID)) {
254 sendRST_STREAM(streamID, CANCEL);
255 markAsReset(streamID);
256 deleteActiveStream(streamID);
257 }
258}
259
260void QHttp2ProtocolHandler::_q_uploadDataDestroyed(QObject *uploadData)
261{
262 streamIDs.remove(uploadData);
263}
264
265void QHttp2ProtocolHandler::_q_readyRead()
266{
267 _q_receiveReply();
268}
269
270void QHttp2ProtocolHandler::_q_receiveReply()
271{
272 Q_ASSERT(m_socket);
273 Q_ASSERT(m_channel);
274
275 while (!goingAway || activeStreams.size()) {
276 const auto result = frameReader.read(*m_socket);
277 switch (result) {
278 case FrameStatus::incompleteFrame:
279 return;
280 case FrameStatus::protocolError:
281 return connectionError(PROTOCOL_ERROR, "invalid frame");
282 case FrameStatus::sizeError:
283 return connectionError(FRAME_SIZE_ERROR, "invalid frame size");
284 default:
285 break;
286 }
287
288 Q_ASSERT(result == FrameStatus::goodFrame);
289
290 inboundFrame = std::move(frameReader.inboundFrame());
291
292 const auto frameType = inboundFrame.type();
293 if (continuationExpected && frameType != FrameType::CONTINUATION)
294 return connectionError(PROTOCOL_ERROR, "CONTINUATION expected");
295
296 switch (frameType) {
297 case FrameType::DATA:
298 handleDATA();
299 break;
300 case FrameType::HEADERS:
301 handleHEADERS();
302 break;
303 case FrameType::PRIORITY:
304 handlePRIORITY();
305 break;
306 case FrameType::RST_STREAM:
307 handleRST_STREAM();
308 break;
309 case FrameType::SETTINGS:
310 handleSETTINGS();
311 break;
312 case FrameType::PUSH_PROMISE:
313 handlePUSH_PROMISE();
314 break;
315 case FrameType::PING:
316 handlePING();
317 break;
318 case FrameType::GOAWAY:
319 handleGOAWAY();
320 break;
321 case FrameType::WINDOW_UPDATE:
322 handleWINDOW_UPDATE();
323 break;
324 case FrameType::CONTINUATION:
325 handleCONTINUATION();
326 break;
327 case FrameType::LAST_FRAME_TYPE:
328 // 5.1 - ignore unknown frames.
329 break;
330 }
331 }
332}
333
334bool QHttp2ProtocolHandler::sendRequest()
335{
336 if (goingAway) {
337 // Stop further calls to this method: we have received GOAWAY
338 // so we cannot create new streams.
339 m_channel->emitFinishedWithError(QNetworkReply::ProtocolUnknownError,
340 "GOAWAY received, cannot start a request");
341 m_channel->h2RequestsToSend.clear();
342 return false;
343 }
344
345 // Process 'fake' (created by QNetworkAccessManager::connectToHostEncrypted())
346 // requests first:
347 auto &requests = m_channel->h2RequestsToSend;
348 for (auto it = requests.begin(), endIt = requests.end(); it != endIt;) {
349 const auto &pair = *it;
350 const QString scheme(pair.first.url().scheme());
351 if (scheme == QLatin1String("preconnect-http")
352 || scheme == QLatin1String("preconnect-https")) {
353 m_connection->preConnectFinished();
354 emit pair.second->finished();
355 it = requests.erase(it);
356 if (!requests.size()) {
357 // Normally, after a connection was established and H2
358 // was negotiated, we send a client preface. connectToHostEncrypted
359 // though is not meant to send any data, it's just a 'preconnect'.
360 // Thus we return early:
361 return true;
362 }
363 } else {
364 ++it;
365 }
366 }
367
368 if (!prefaceSent && !sendClientPreface())
369 return false;
370
371 if (!requests.size())
372 return true;
373
374 m_channel->state = QHttpNetworkConnectionChannel::WritingState;
375 // Check what was promised/pushed, maybe we do not have to send a request
376 // and have a response already?
377
378 for (auto it = requests.begin(), endIt = requests.end(); it != endIt;) {
379 const auto key = urlkey_from_request(it->first).toString();
380 if (!promisedData.contains(key)) {
381 ++it;
382 continue;
383 }
384 // Woo-hoo, we do not have to ask, the answer is ready for us:
385 HttpMessagePair message = *it;
386 it = requests.erase(it);
387 initReplyFromPushPromise(message, key);
388 }
389
390 const auto streamsToUse = std::min<quint32>(maxConcurrentStreams - activeStreams.size(),
391 requests.size());
392 auto it = requests.begin();
393 for (quint32 i = 0; i < streamsToUse; ++i) {
394 const qint32 newStreamID = createNewStream(*it);
395 if (!newStreamID) {
396 // TODO: actually we have to open a new connection.
397 qCCritical(QT_HTTP2, "sendRequest: out of stream IDs");
398 break;
399 }
400
401 it = requests.erase(it);
402
403 Stream &newStream = activeStreams[newStreamID];
404 if (!sendHEADERS(newStream)) {
405 finishStreamWithError(newStream, QNetworkReply::UnknownNetworkError,
406 QLatin1String("failed to send HEADERS frame(s)"));
407 deleteActiveStream(newStreamID);
408 continue;
409 }
410
411 if (newStream.data() && !sendDATA(newStream)) {
412 finishStreamWithError(newStream, QNetworkReply::UnknownNetworkError,
413 QLatin1String("failed to send DATA frame(s)"));
414 sendRST_STREAM(newStreamID, INTERNAL_ERROR);
415 markAsReset(newStreamID);
416 deleteActiveStream(newStreamID);
417 }
418 }
419
420 m_channel->state = QHttpNetworkConnectionChannel::IdleState;
421
422 return true;
423}
424
425
426bool QHttp2ProtocolHandler::sendClientPreface()
427{
428 // 3.5 HTTP/2 Connection Preface
429 Q_ASSERT(m_socket);
430
431 if (prefaceSent)
432 return true;
433
434 const qint64 written = m_socket->write(Http2::Http2clientPreface,
435 Http2::clientPrefaceLength);
436 if (written != Http2::clientPrefaceLength)
437 return false;
438
439 // 6.5 SETTINGS
440 frameWriter.setOutboundFrame(Http2::configurationToSettingsFrame(m_connection->http2Parameters()));
441 Q_ASSERT(frameWriter.outboundFrame().payloadSize());
442
443 if (!frameWriter.write(*m_socket))
444 return false;
445
446 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
447 // We only send WINDOW_UPDATE for the connection if the size differs from the
448 // default 64 KB:
449 const auto delta = maxSessionReceiveWindowSize - Http2::defaultSessionWindowSize;
450 if (delta && !sendWINDOW_UPDATE(Http2::connectionStreamID, delta))
451 return false;
452
453 prefaceSent = true;
454 waitingForSettingsACK = true;
455
456 return true;
457}
458
459bool QHttp2ProtocolHandler::sendSETTINGS_ACK()
460{
461 Q_ASSERT(m_socket);
462
463 if (!prefaceSent && !sendClientPreface())
464 return false;
465
466 frameWriter.start(FrameType::SETTINGS, FrameFlag::ACK, Http2::connectionStreamID);
467
468 return frameWriter.write(*m_socket);
469}
470
471bool QHttp2ProtocolHandler::sendHEADERS(Stream &stream)
472{
473 using namespace HPack;
474
475 frameWriter.start(FrameType::HEADERS, FrameFlag::PRIORITY | FrameFlag::END_HEADERS,
476 stream.streamID);
477
478 if (!stream.data()) {
479 frameWriter.addFlag(FrameFlag::END_STREAM);
480 stream.state = Stream::halfClosedLocal;
481 } else {
482 stream.state = Stream::open;
483 }
484
485 frameWriter.append(quint32()); // No stream dependency in Qt.
486 frameWriter.append(stream.weight());
487
488 bool useProxy = false;
489#ifndef QT_NO_NETWORKPROXY
490 useProxy = m_connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy;
491#endif
492 const auto headers = build_headers(stream.request(), maxHeaderListSize, useProxy);
493 if (!headers.size()) // nothing fits into maxHeaderListSize
494 return false;
495
496 // Compress in-place:
497 BitOStream outputStream(frameWriter.outboundFrame().buffer);
498 if (!encoder.encodeRequest(outputStream, headers))
499 return false;
500
501 return frameWriter.writeHEADERS(*m_socket, maxFrameSize);
502}
503
504bool QHttp2ProtocolHandler::sendDATA(Stream &stream)
505{
506 Q_ASSERT(maxFrameSize > frameHeaderSize);
507 Q_ASSERT(m_socket);
508 Q_ASSERT(stream.data());
509
510 const auto &request = stream.request();
511 auto reply = stream.reply();
512 Q_ASSERT(reply);
513 const auto replyPrivate = reply->d_func();
514 Q_ASSERT(replyPrivate);
515
516 auto slot = std::min<qint32>(sessionSendWindowSize, stream.sendWindow);
517 while (!stream.data()->atEnd() && slot) {
518 qint64 chunkSize = 0;
519 const uchar *src =
520 reinterpret_cast<const uchar *>(stream.data()->readPointer(slot, chunkSize));
521
522 if (chunkSize == -1)
523 return false;
524
525 if (!src || !chunkSize) {
526 // Stream is not suspended by the flow control,
527 // we do not have data ready yet.
528 return true;
529 }
530
531 frameWriter.start(FrameType::DATA, FrameFlag::EMPTY, stream.streamID);
532 const qint32 bytesWritten = std::min<qint32>(slot, chunkSize);
533
534 if (!frameWriter.writeDATA(*m_socket, maxFrameSize, src, bytesWritten))
535 return false;
536
537 stream.data()->advanceReadPointer(bytesWritten);
538 stream.sendWindow -= bytesWritten;
539 sessionSendWindowSize -= bytesWritten;
540 replyPrivate->totallyUploadedData += bytesWritten;
541 emit reply->dataSendProgress(replyPrivate->totallyUploadedData,
542 request.contentLength());
543 slot = std::min(sessionSendWindowSize, stream.sendWindow);
544 }
545
546 if (replyPrivate->totallyUploadedData == request.contentLength()) {
547 frameWriter.start(FrameType::DATA, FrameFlag::END_STREAM, stream.streamID);
548 frameWriter.setPayloadSize(0);
549 frameWriter.write(*m_socket);
550 stream.state = Stream::halfClosedLocal;
551 stream.data()->disconnect(this);
552 removeFromSuspended(stream.streamID);
553 } else if (!stream.data()->atEnd()) {
554 addToSuspended(stream);
555 }
556
557 return true;
558}
559
560bool QHttp2ProtocolHandler::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
561{
562 Q_ASSERT(m_socket);
563
564 frameWriter.start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID);
565 frameWriter.append(delta);
566 return frameWriter.write(*m_socket);
567}
568
569bool QHttp2ProtocolHandler::sendRST_STREAM(quint32 streamID, quint32 errorCode)
570{
571 Q_ASSERT(m_socket);
572
573 frameWriter.start(FrameType::RST_STREAM, FrameFlag::EMPTY, streamID);
574 frameWriter.append(errorCode);
575 return frameWriter.write(*m_socket);
576}
577
578bool QHttp2ProtocolHandler::sendGOAWAY(quint32 errorCode)
579{
580 Q_ASSERT(m_socket);
581
582 frameWriter.start(FrameType::GOAWAY, FrameFlag::EMPTY, connectionStreamID);
583 frameWriter.append(quint32(connectionStreamID));
584 frameWriter.append(errorCode);
585 return frameWriter.write(*m_socket);
586}
587
588void QHttp2ProtocolHandler::handleDATA()
589{
590 Q_ASSERT(inboundFrame.type() == FrameType::DATA);
591
592 const auto streamID = inboundFrame.streamID();
593 if (streamID == connectionStreamID)
594 return connectionError(PROTOCOL_ERROR, "DATA on stream 0x0");
595
596 if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
597 return connectionError(ENHANCE_YOUR_CALM, "DATA on invalid stream");
598
599 if (qint32(inboundFrame.payloadSize()) > sessionReceiveWindowSize)
600 return connectionError(FLOW_CONTROL_ERROR, "Flow control error");
601
602 sessionReceiveWindowSize -= inboundFrame.payloadSize();
603
604 if (activeStreams.contains(streamID)) {
605 auto &stream = activeStreams[streamID];
606
607 if (qint32(inboundFrame.payloadSize()) > stream.recvWindow) {
608 finishStreamWithError(stream, QNetworkReply::ProtocolFailure,
609 QLatin1String("flow control error"));
610 sendRST_STREAM(streamID, FLOW_CONTROL_ERROR);
611 markAsReset(streamID);
612 deleteActiveStream(streamID);
613 } else {
614 stream.recvWindow -= inboundFrame.payloadSize();
615 // Uncompress data if needed and append it ...
616 updateStream(stream, inboundFrame);
617
618 if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) {
619 finishStream(stream);
620 deleteActiveStream(stream.streamID);
621 } else if (stream.recvWindow < streamInitialReceiveWindowSize / 2) {
622 QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection,
623 Q_ARG(quint32, stream.streamID),
624 Q_ARG(quint32, streamInitialReceiveWindowSize - stream.recvWindow));
625 stream.recvWindow = streamInitialReceiveWindowSize;
626 }
627 }
628 }
629
630 if (sessionReceiveWindowSize < maxSessionReceiveWindowSize / 2) {
631 QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection,
632 Q_ARG(quint32, connectionStreamID),
633 Q_ARG(quint32, maxSessionReceiveWindowSize - sessionReceiveWindowSize));
634 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
635 }
636}
637
638void QHttp2ProtocolHandler::handleHEADERS()
639{
640 Q_ASSERT(inboundFrame.type() == FrameType::HEADERS);
641
642 const auto streamID = inboundFrame.streamID();
643 if (streamID == connectionStreamID)
644 return connectionError(PROTOCOL_ERROR, "HEADERS on 0x0 stream");
645
646 if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
647 return connectionError(ENHANCE_YOUR_CALM, "HEADERS on invalid stream");
648
649 const auto flags = inboundFrame.flags();
650 if (flags.testFlag(FrameFlag::PRIORITY)) {
651 handlePRIORITY();
652 if (goingAway)
653 return;
654 }
655
656 const bool endHeaders = flags.testFlag(FrameFlag::END_HEADERS);
657 continuedFrames.clear();
658 continuedFrames.push_back(std::move(inboundFrame));
659 if (!endHeaders) {
660 continuationExpected = true;
661 return;
662 }
663
664 handleContinuedHEADERS();
665}
666
667void QHttp2ProtocolHandler::handlePRIORITY()
668{
669 Q_ASSERT(inboundFrame.type() == FrameType::PRIORITY ||
670 inboundFrame.type() == FrameType::HEADERS);
671
672 const auto streamID = inboundFrame.streamID();
673 if (streamID == connectionStreamID)
674 return connectionError(PROTOCOL_ERROR, "PIRORITY on 0x0 stream");
675
676 if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
677 return connectionError(ENHANCE_YOUR_CALM, "PRIORITY on invalid stream");
678
679 quint32 streamDependency = 0;
680 uchar weight = 0;
681 const bool noErr = inboundFrame.priority(&streamDependency, &weight);
682 Q_UNUSED(noErr);
683 Q_ASSERT(noErr);
684
685
686 const bool exclusive = streamDependency & 0x80000000;
687 streamDependency &= ~0x80000000;
688
689 // Ignore this for now ...
690 // Can be used for streams (re)prioritization - 5.3
691 Q_UNUSED(exclusive);
692 Q_UNUSED(weight);
693}
694
695void QHttp2ProtocolHandler::handleRST_STREAM()
696{
697 Q_ASSERT(inboundFrame.type() == FrameType::RST_STREAM);
698
699 // "RST_STREAM frames MUST be associated with a stream.
700 // If a RST_STREAM frame is received with a stream identifier of 0x0,
701 // the recipient MUST treat this as a connection error (Section 5.4.1)
702 // of type PROTOCOL_ERROR.
703 const auto streamID = inboundFrame.streamID();
704 if (streamID == connectionStreamID)
705 return connectionError(PROTOCOL_ERROR, "RST_STREAM on 0x0");
706
707 if (!(streamID & 0x1)) {
708 // RST_STREAM on a promised stream:
709 // since we do not keep track of such streams,
710 // just ignore.
711 return;
712 }
713
714 if (streamID >= nextID) {
715 // "RST_STREAM frames MUST NOT be sent for a stream
716 // in the "idle" state. .. the recipient MUST treat this
717 // as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
718 return connectionError(PROTOCOL_ERROR, "RST_STREAM on idle stream");
719 }
720
721 if (!activeStreams.contains(streamID)) {
722 // 'closed' stream, ignore.
723 return;
724 }
725
726 Q_ASSERT(inboundFrame.dataSize() == 4);
727
728 Stream &stream = activeStreams[streamID];
729 finishStreamWithError(stream, qFromBigEndian<quint32>(inboundFrame.dataBegin()));
730 markAsReset(stream.streamID);
731 deleteActiveStream(stream.streamID);
732}
733
734void QHttp2ProtocolHandler::handleSETTINGS()
735{
736 // 6.5 SETTINGS.
737 Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS);
738
739 if (inboundFrame.streamID() != connectionStreamID)
740 return connectionError(PROTOCOL_ERROR, "SETTINGS on invalid stream");
741
742 if (inboundFrame.flags().testFlag(FrameFlag::ACK)) {
743 if (!waitingForSettingsACK)
744 return connectionError(PROTOCOL_ERROR, "unexpected SETTINGS ACK");
745 waitingForSettingsACK = false;
746 return;
747 }
748
749 if (inboundFrame.dataSize()) {
750 auto src = inboundFrame.dataBegin();
751 for (const uchar *end = src + inboundFrame.dataSize(); src != end; src += 6) {
752 const Settings identifier = Settings(qFromBigEndian<quint16>(src));
753 const quint32 intVal = qFromBigEndian<quint32>(src + 2);
754 if (!acceptSetting(identifier, intVal)) {
755 // If not accepted - we finish with connectionError.
756 return;
757 }
758 }
759 }
760
761 sendSETTINGS_ACK();
762}
763
764
765void QHttp2ProtocolHandler::handlePUSH_PROMISE()
766{
767 // 6.6 PUSH_PROMISE.
768 Q_ASSERT(inboundFrame.type() == FrameType::PUSH_PROMISE);
769
770 if (!pushPromiseEnabled && prefaceSent && !waitingForSettingsACK) {
771 // This means, server ACKed our 'NO PUSH',
772 // but sent us PUSH_PROMISE anyway.
773 return connectionError(PROTOCOL_ERROR, "unexpected PUSH_PROMISE frame");
774 }
775
776 const auto streamID = inboundFrame.streamID();
777 if (streamID == connectionStreamID) {
778 return connectionError(PROTOCOL_ERROR,
779 "PUSH_PROMISE with invalid associated stream (0x0)");
780 }
781
782 if (!activeStreams.contains(streamID) && !streamWasReset(streamID)) {
783 return connectionError(ENHANCE_YOUR_CALM,
784 "PUSH_PROMISE with invalid associated stream");
785 }
786
787 const auto reservedID = qFromBigEndian<quint32>(inboundFrame.dataBegin());
788 if ((reservedID & 1) || reservedID <= lastPromisedID ||
789 reservedID > Http2::lastValidStreamID) {
790 return connectionError(PROTOCOL_ERROR,
791 "PUSH_PROMISE with invalid promised stream ID");
792 }
793
794 lastPromisedID = reservedID;
795
796 if (!pushPromiseEnabled) {
797 // "ignoring a PUSH_PROMISE frame causes the stream state to become
798 // indeterminate" - let's send RST_STREAM frame with REFUSE_STREAM code.
799 resetPromisedStream(inboundFrame, Http2::REFUSE_STREAM);
800 }
801
802 const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
803 continuedFrames.clear();
804 continuedFrames.push_back(std::move(inboundFrame));
805
806 if (!endHeaders) {
807 continuationExpected = true;
808 return;
809 }
810
811 handleContinuedHEADERS();
812}
813
814void QHttp2ProtocolHandler::handlePING()
815{
816 // Since we're implementing a client and not
817 // a server, we only reply to a PING, ACKing it.
818 Q_ASSERT(inboundFrame.type() == FrameType::PING);
819 Q_ASSERT(m_socket);
820
821 if (inboundFrame.streamID() != connectionStreamID)
822 return connectionError(PROTOCOL_ERROR, "PING on invalid stream");
823
824 if (inboundFrame.flags() & FrameFlag::ACK)
825 return connectionError(PROTOCOL_ERROR, "unexpected PING ACK");
826
827 Q_ASSERT(inboundFrame.dataSize() == 8);
828
829 frameWriter.start(FrameType::PING, FrameFlag::ACK, connectionStreamID);
830 frameWriter.append(inboundFrame.dataBegin(), inboundFrame.dataBegin() + 8);
831 frameWriter.write(*m_socket);
832}
833
834void QHttp2ProtocolHandler::handleGOAWAY()
835{
836 // 6.8 GOAWAY
837
838 Q_ASSERT(inboundFrame.type() == FrameType::GOAWAY);
839 // "An endpoint MUST treat a GOAWAY frame with a stream identifier
840 // other than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
841 if (inboundFrame.streamID() != connectionStreamID)
842 return connectionError(PROTOCOL_ERROR, "GOAWAY on invalid stream");
843
844 const auto src = inboundFrame.dataBegin();
845 quint32 lastStreamID = qFromBigEndian<quint32>(src);
846 const quint32 errorCode = qFromBigEndian<quint32>(src + 4);
847
848 if (!lastStreamID) {
849 // "The last stream identifier can be set to 0 if no
850 // streams were processed."
851 lastStreamID = 1;
852 } else if (!(lastStreamID & 0x1)) {
853 // 5.1.1 - we (client) use only odd numbers as stream identifiers.
854 return connectionError(PROTOCOL_ERROR, "GOAWAY with invalid last stream ID");
855 } else if (lastStreamID >= nextID) {
856 // "A server that is attempting to gracefully shut down a connection SHOULD
857 // send an initial GOAWAY frame with the last stream identifier set to 2^31-1
858 // and a NO_ERROR code."
859 if (lastStreamID != Http2::lastValidStreamID || errorCode != HTTP2_NO_ERROR)
860 return connectionError(PROTOCOL_ERROR, "GOAWAY invalid stream/error code");
861 } else {
862 lastStreamID += 2;
863 }
864
865 goingAway = true;
866
867 // For the requests (and streams) we did not start yet, we have to report an
868 // error.
869 m_channel->emitFinishedWithError(QNetworkReply::ProtocolUnknownError,
870 "GOAWAY received, cannot start a request");
871 // Also, prevent further calls to sendRequest:
872 m_channel->h2RequestsToSend.clear();
873
874 QNetworkReply::NetworkError error = QNetworkReply::NoError;
875 QString message;
876 qt_error(errorCode, error, message);
877
878 // Even if the GOAWAY frame contains NO_ERROR we must send an error
879 // when terminating streams to ensure users can distinguish from a
880 // successful completion.
881 if (errorCode == HTTP2_NO_ERROR) {
882 error = QNetworkReply::ContentReSendError;
883 message = QLatin1String("Server stopped accepting new streams before this stream was established");
884 }
885
886 for (quint32 id = lastStreamID; id < nextID; id += 2) {
887 const auto it = activeStreams.find(id);
888 if (it != activeStreams.end()) {
889 Stream &stream = *it;
890 finishStreamWithError(stream, error, message);
891 markAsReset(id);
892 deleteActiveStream(id);
893 } else {
894 removeFromSuspended(id);
895 }
896 }
897
898 if (!activeStreams.size())
899 closeSession();
900}
901
902void QHttp2ProtocolHandler::handleWINDOW_UPDATE()
903{
904 Q_ASSERT(inboundFrame.type() == FrameType::WINDOW_UPDATE);
905
906
907 const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
908 const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
909 const auto streamID = inboundFrame.streamID();
910
911 if (streamID == Http2::connectionStreamID) {
912 if (!valid || sum_will_overflow(sessionSendWindowSize, delta))
913 return connectionError(PROTOCOL_ERROR, "WINDOW_UPDATE invalid delta");
914 sessionSendWindowSize += delta;
915 } else {
916 if (!activeStreams.contains(streamID)) {
917 // WINDOW_UPDATE on closed streams can be ignored.
918 return;
919 }
920 auto &stream = activeStreams[streamID];
921 if (!valid || sum_will_overflow(stream.sendWindow, delta)) {
922 finishStreamWithError(stream, QNetworkReply::ProtocolFailure,
923 QLatin1String("invalid WINDOW_UPDATE delta"));
924 sendRST_STREAM(streamID, PROTOCOL_ERROR);
925 markAsReset(streamID);
926 deleteActiveStream(streamID);
927 return;
928 }
929 stream.sendWindow += delta;
930 }
931
932 // Since we're in _q_receiveReply at the moment, let's first handle other
933 // frames and resume suspended streams (if any) == start sending our own frame
934 // after handling these frames, since one them can be e.g. GOAWAY.
935 QMetaObject::invokeMethod(this, "resumeSuspendedStreams", Qt::QueuedConnection);
936}
937
938void QHttp2ProtocolHandler::handleCONTINUATION()
939{
940 Q_ASSERT(inboundFrame.type() == FrameType::CONTINUATION);
941 Q_ASSERT(continuedFrames.size()); // HEADERS frame must be already in.
942
943 if (inboundFrame.streamID() != continuedFrames.front().streamID())
944 return connectionError(PROTOCOL_ERROR, "CONTINUATION on invalid stream");
945
946 const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
947 continuedFrames.push_back(std::move(inboundFrame));
948
949 if (!endHeaders)
950 return;
951
952 continuationExpected = false;
953 handleContinuedHEADERS();
954}
955
956void QHttp2ProtocolHandler::handleContinuedHEADERS()
957{
958 // 'Continued' HEADERS can be: the initial HEADERS/PUSH_PROMISE frame
959 // with/without END_HEADERS flag set plus, if no END_HEADERS flag,
960 // a sequence of one or more CONTINUATION frames.
961 Q_ASSERT(continuedFrames.size());
962 const auto firstFrameType = continuedFrames[0].type();
963 Q_ASSERT(firstFrameType == FrameType::HEADERS ||
964 firstFrameType == FrameType::PUSH_PROMISE);
965
966 const auto streamID = continuedFrames[0].streamID();
967
968 if (firstFrameType == FrameType::HEADERS) {
969 if (activeStreams.contains(streamID)) {
970 Stream &stream = activeStreams[streamID];
971 if (stream.state != Stream::halfClosedLocal
972 && stream.state != Stream::remoteReserved
973 && stream.state != Stream::open) {
974 // We can receive HEADERS on streams initiated by our requests
975 // (these streams are in halfClosedLocal or open state) or
976 // remote-reserved streams from a server's PUSH_PROMISE.
977 finishStreamWithError(stream, QNetworkReply::ProtocolFailure,
978 QLatin1String("HEADERS on invalid stream"));
979 sendRST_STREAM(streamID, CANCEL);
980 markAsReset(streamID);
981 deleteActiveStream(streamID);
982 return;
983 }
984 } else if (!streamWasReset(streamID)) {
985 return connectionError(PROTOCOL_ERROR, "HEADERS on invalid stream");
986 }
987 // Else: we cannot just ignore our peer's HEADERS frames - they change
988 // HPACK context - even though the stream was reset; apparently the peer
989 // has yet to see the reset.
990 }
991
992 std::vector<uchar> hpackBlock(assemble_hpack_block(continuedFrames));
993 if (!hpackBlock.size()) {
994 // It could be a PRIORITY sent in HEADERS - already handled by this
995 // point in handleHEADERS. If it was PUSH_PROMISE (HTTP/2 8.2.1):
996 // "The header fields in PUSH_PROMISE and any subsequent CONTINUATION
997 // frames MUST be a valid and complete set of request header fields
998 // (Section 8.1.2.3) ... If a client receives a PUSH_PROMISE that does
999 // not include a complete and valid set of header fields or the :method
1000 // pseudo-header field identifies a method that is not safe, it MUST
1001 // respond with a stream error (Section 5.4.2) of type PROTOCOL_ERROR."
1002 if (firstFrameType == FrameType::PUSH_PROMISE)
1003 resetPromisedStream(continuedFrames[0], Http2::PROTOCOL_ERROR);
1004
1005 return;
1006 }
1007
1008 HPack::BitIStream inputStream{&hpackBlock[0], &hpackBlock[0] + hpackBlock.size()};
1009 if (!decoder.decodeHeaderFields(inputStream))
1010 return connectionError(COMPRESSION_ERROR, "HPACK decompression failed");
1011
1012 switch (firstFrameType) {
1013 case FrameType::HEADERS:
1014 if (activeStreams.contains(streamID)) {
1015 Stream &stream = activeStreams[streamID];
1016 updateStream(stream, decoder.decodedHeader());
1017 // No DATA frames.
1018 if (continuedFrames[0].flags() & FrameFlag::END_STREAM) {
1019 finishStream(stream);
1020 deleteActiveStream(stream.streamID);
1021 }
1022 }
1023 break;
1024 case FrameType::PUSH_PROMISE:
1025 if (!tryReserveStream(continuedFrames[0], decoder.decodedHeader()))
1026 resetPromisedStream(continuedFrames[0], Http2::PROTOCOL_ERROR);
1027 break;
1028 default:
1029 break;
1030 }
1031}
1032
1033bool QHttp2ProtocolHandler::acceptSetting(Http2::Settings identifier, quint32 newValue)
1034{
1035 if (identifier == Settings::HEADER_TABLE_SIZE_ID) {
1036 if (newValue > maxAcceptableTableSize) {
1037 connectionError(PROTOCOL_ERROR, "SETTINGS invalid table size");
1038 return false;
1039 }
1040 encoder.setMaxDynamicTableSize(newValue);
1041 }
1042
1043 if (identifier == Settings::INITIAL_WINDOW_SIZE_ID) {
1044 // For every active stream - adjust its window
1045 // (and handle possible overflows as errors).
1046 if (newValue > quint32(std::numeric_limits<qint32>::max())) {
1047 connectionError(FLOW_CONTROL_ERROR, "SETTINGS invalid initial window size");
1048 return false;
1049 }
1050
1051 const qint32 delta = qint32(newValue) - streamInitialSendWindowSize;
1052 streamInitialSendWindowSize = newValue;
1053
1054 std::vector<quint32> brokenStreams;
1055 brokenStreams.reserve(activeStreams.size());
1056 for (auto &stream : activeStreams) {
1057 if (sum_will_overflow(stream.sendWindow, delta)) {
1058 brokenStreams.push_back(stream.streamID);
1059 continue;
1060 }
1061 stream.sendWindow += delta;
1062 }
1063
1064 for (auto id : brokenStreams) {
1065 auto &stream = activeStreams[id];
1066 finishStreamWithError(stream, QNetworkReply::ProtocolFailure,
1067 QLatin1String("SETTINGS window overflow"));
1068 sendRST_STREAM(id, PROTOCOL_ERROR);
1069 markAsReset(id);
1070 deleteActiveStream(id);
1071 }
1072
1073 QMetaObject::invokeMethod(this, "resumeSuspendedStreams", Qt::QueuedConnection);
1074 }
1075
1076 if (identifier == Settings::MAX_CONCURRENT_STREAMS_ID) {
1077 if (newValue > maxPeerConcurrentStreams) {
1078 connectionError(PROTOCOL_ERROR, "SETTINGS invalid number of concurrent streams");
1079 return false;
1080 }
1081 maxConcurrentStreams = newValue;
1082 }
1083
1084 if (identifier == Settings::MAX_FRAME_SIZE_ID) {
1085 if (newValue < Http2::minPayloadLimit || newValue > Http2::maxPayloadSize) {
1086 connectionError(PROTOCOL_ERROR, "SETTGINGS max frame size is out of range");
1087 return false;
1088 }
1089 maxFrameSize = newValue;
1090 }
1091
1092 if (identifier == Settings::MAX_HEADER_LIST_SIZE_ID) {
1093 // We just remember this value, it can later
1094 // prevent us from sending any request (and this
1095 // will end up in request/reply error).
1096 maxHeaderListSize = newValue;
1097 }
1098
1099 return true;
1100}
1101
1102void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader &headers,
1103 Qt::ConnectionType connectionType)
1104{
1105 const auto httpReply = stream.reply();
1106 const auto &httpRequest = stream.request();
1107 Q_ASSERT(httpReply || stream.state == Stream::remoteReserved);
1108
1109 if (!httpReply) {
1110 // It's a PUSH_PROMISEd HEADERS, no actual request/reply
1111 // exists yet, we have to cache this data for a future
1112 // (potential) request.
1113
1114 // TODO: the part with assignment is not especially cool
1115 // or beautiful, good that at least QByteArray is implicitly
1116 // sharing data. To be refactored (std::move).
1117 Q_ASSERT(promisedData.contains(stream.key));
1118 PushPromise &promise = promisedData[stream.key];
1119 promise.responseHeader = headers;
1120 return;
1121 }
1122
1123 const auto httpReplyPrivate = httpReply->d_func();
1124
1125 // For HTTP/1 'location' is handled (and redirect URL set) when a protocol
1126 // handler emits channel->allDone(). Http/2 protocol handler never emits
1127 // allDone, since we have many requests multiplexed in one channel at any
1128 // moment and we are probably not done yet. So we extract url and set it
1129 // here, if needed.
1130 int statusCode = 0;
1131 QUrl redirectUrl;
1132
1133 for (const auto &pair : headers) {
1134 const auto &name = pair.name;
1135 auto value = pair.value;
1136
1137 // TODO: part of this code copies what SPDY protocol handler does when
1138 // processing headers. Binary nature of HTTP/2 and SPDY saves us a lot
1139 // of parsing and related errors/bugs, but it would be nice to have
1140 // more detailed validation of headers.
1141 if (name == ":status") {
1142 statusCode = value.left(3).toInt();
1143 httpReply->setStatusCode(statusCode);
1144 httpReplyPrivate->reasonPhrase = QString::fromLatin1(value.mid(4));
1145 } else if (name == ":version") {
1146 httpReplyPrivate->majorVersion = value.at(5) - '0';
1147 httpReplyPrivate->minorVersion = value.at(7) - '0';
1148 } else if (name == "content-length") {
1149 bool ok = false;
1150 const qlonglong length = value.toLongLong(&ok);
1151 if (ok)
1152 httpReply->setContentLength(length);
1153 } else {
1154 if (name == "location")
1155 redirectUrl = QUrl::fromEncoded(value);
1156 QByteArray binder(", ");
1157 if (name == "set-cookie")
1158 binder = "\n";
1159 httpReplyPrivate->fields.append(qMakePair(name, value.replace('\0', binder)));
1160 }
1161 }
1162
1163 if (QHttpNetworkReply::isHttpRedirect(statusCode) && redirectUrl.isValid())
1164 httpReply->setRedirectUrl(redirectUrl);
1165
1166 if (httpReplyPrivate->isCompressed() && httpRequest.d->autoDecompress) {
1167 httpReplyPrivate->removeAutoDecompressHeader();
1168 httpReplyPrivate->decompressHelper.setEncoding(
1169 httpReplyPrivate->headerField("content-encoding"));
1170 if (httpReplyPrivate->request.ignoreDecompressionRatio())
1171 httpReplyPrivate->decompressHelper.setArchiveBombDetectionEnabled(false);
1172 }
1173
1174 if (QHttpNetworkReply::isHttpRedirect(statusCode)
1175 || statusCode == 401 || statusCode == 407) {
1176 // These are the status codes that can trigger uploadByteDevice->reset()
1177 // in QHttpNetworkConnectionChannel::handleStatus. Alas, we have no
1178 // single request/reply, we multiplex several requests and thus we never
1179 // simply call 'handleStatus'. If we have byte-device - we try to reset
1180 // it here, we don't (and can't) handle any error during reset operation.
1181 if (stream.data())
1182 stream.data()->reset();
1183 }
1184
1185 if (connectionType == Qt::DirectConnection)
1186 emit httpReply->headerChanged();
1187 else
1188 QMetaObject::invokeMethod(httpReply, "headerChanged", connectionType);
1189}
1190
1191void QHttp2ProtocolHandler::updateStream(Stream &stream, const Frame &frame,
1192 Qt::ConnectionType connectionType)
1193{
1194 Q_ASSERT(frame.type() == FrameType::DATA);
1195 auto httpReply = stream.reply();
1196 Q_ASSERT(httpReply || stream.state == Stream::remoteReserved);
1197
1198 if (!httpReply) {
1199 Q_ASSERT(promisedData.contains(stream.key));
1200 PushPromise &promise = promisedData[stream.key];
1201 // TODO: refactor this to use std::move.
1202 promise.dataFrames.push_back(frame);
1203 return;
1204 }
1205
1206 if (const auto length = frame.dataSize()) {
1207 const char *data = reinterpret_cast<const char *>(frame.dataBegin());
1208 auto &httpRequest = stream.request();
1209 auto replyPrivate = httpReply->d_func();
1210
1211 replyPrivate->totalProgress += length;
1212
1213 const QByteArray wrapped(data, length);
1214 if (httpRequest.d->autoDecompress && replyPrivate->isCompressed()) {
1215 Q_ASSERT(replyPrivate->decompressHelper.isValid());
1216
1217 replyPrivate->decompressHelper.feed(wrapped);
1218 while (replyPrivate->decompressHelper.hasData()) {
1219 QByteArray output(4 * 1024, Qt::Uninitialized);
1220 qint64 read = replyPrivate->decompressHelper.read(output.data(), output.size());
1221 if (read > 0) {
1222 output.resize(read);
1223 replyPrivate->responseData.append(std::move(output));
1224 }
1225 }
1226 } else {
1227 replyPrivate->responseData.append(wrapped);
1228 }
1229
1230 if (replyPrivate->shouldEmitSignals()) {
1231 if (connectionType == Qt::DirectConnection) {
1232 emit httpReply->readyRead();
1233 emit httpReply->dataReadProgress(replyPrivate->totalProgress,
1234 replyPrivate->bodyLength);
1235 } else {
1236 QMetaObject::invokeMethod(httpReply, "readyRead", connectionType);
1237 QMetaObject::invokeMethod(httpReply, "dataReadProgress", connectionType,
1238 Q_ARG(qint64, replyPrivate->totalProgress),
1239 Q_ARG(qint64, replyPrivate->bodyLength));
1240 }
1241 }
1242 }
1243}
1244
1245void QHttp2ProtocolHandler::finishStream(Stream &stream, Qt::ConnectionType connectionType)
1246{
1247 Q_ASSERT(stream.state == Stream::remoteReserved || stream.reply());
1248
1249 stream.state = Stream::closed;
1250 auto httpReply = stream.reply();
1251 if (httpReply) {
1252 httpReply->disconnect(this);
1253 if (stream.data())
1254 stream.data()->disconnect(this);
1255
1256 if (connectionType == Qt::DirectConnection)
1257 emit httpReply->finished();
1258 else
1259 QMetaObject::invokeMethod(httpReply, "finished", connectionType);
1260 }
1261
1262 qCDebug(QT_HTTP2) << "stream" << stream.streamID << "closed";
1263}
1264
1265void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, quint32 errorCode)
1266{
1267 QNetworkReply::NetworkError error = QNetworkReply::NoError;
1268 QString message;
1269 qt_error(errorCode, error, message);
1270 finishStreamWithError(stream, error, message);
1271}
1272
1273void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, QNetworkReply::NetworkError error,
1274 const QString &message)
1275{
1276 Q_ASSERT(stream.state == Stream::remoteReserved || stream.reply());
1277
1278 stream.state = Stream::closed;
1279 if (auto httpReply = stream.reply()) {
1280 httpReply->disconnect(this);
1281 if (stream.data())
1282 stream.data()->disconnect(this);
1283
1284 // TODO: error message must be translated!!! (tr)
1285 emit httpReply->finishedWithError(error, message);
1286 }
1287
1288 qCWarning(QT_HTTP2) << "stream" << stream.streamID
1289 << "finished with error:" << message;
1290}
1291
1292quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message, bool uploadDone)
1293{
1294 const qint32 newStreamID = allocateStreamID();
1295 if (!newStreamID)
1296 return 0;
1297
1298 Q_ASSERT(!activeStreams.contains(newStreamID));
1299
1300 const auto reply = message.second;
1301 const auto replyPrivate = reply->d_func();
1302 replyPrivate->connection = m_connection;
1303 replyPrivate->connectionChannel = m_channel;
1304 reply->setHttp2WasUsed(true);
1305 streamIDs.insert(reply, newStreamID);
1306 connect(reply, SIGNAL(destroyed(QObject*)),
1307 this, SLOT(_q_replyDestroyed(QObject*)));
1308
1309 const Stream newStream(message, newStreamID,
1310 streamInitialSendWindowSize,
1311 streamInitialReceiveWindowSize);
1312
1313 if (!uploadDone) {
1314 if (auto src = newStream.data()) {
1315 connect(src, SIGNAL(readyRead()), this,
1316 SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection);
1317 connect(src, &QHttp2ProtocolHandler::destroyed,
1318 this, &QHttp2ProtocolHandler::_q_uploadDataDestroyed);
1319 streamIDs.insert(src, newStreamID);
1320 }
1321 }
1322
1323 activeStreams.insert(newStreamID, newStream);
1324
1325 return newStreamID;
1326}
1327
1328void QHttp2ProtocolHandler::addToSuspended(Stream &stream)
1329{
1330 qCDebug(QT_HTTP2) << "stream" << stream.streamID
1331 << "suspended by flow control";
1332 const auto priority = stream.priority();
1333 Q_ASSERT(int(priority) >= 0 && int(priority) < 3);
1334 suspendedStreams[priority].push_back(stream.streamID);
1335}
1336
1337void QHttp2ProtocolHandler::markAsReset(quint32 streamID)
1338{
1339 Q_ASSERT(streamID);
1340
1341 qCDebug(QT_HTTP2) << "stream" << streamID << "was reset";
1342 // This part is quite tricky: I have to clear this set
1343 // so that it does not become tOOO big.
1344 if (recycledStreams.size() > maxRecycledStreams) {
1345 // At least, I'm erasing the oldest first ...
1346 recycledStreams.erase(recycledStreams.begin(),
1347 recycledStreams.begin() +
1348 recycledStreams.size() / 2);
1349 }
1350
1351 const auto it = std::lower_bound(recycledStreams.begin(), recycledStreams.end(),
1352 streamID);
1353 if (it != recycledStreams.end() && *it == streamID)
1354 return;
1355
1356 recycledStreams.insert(it, streamID);
1357}
1358
1359quint32 QHttp2ProtocolHandler::popStreamToResume()
1360{
1361 quint32 streamID = connectionStreamID;
1362 using QNR = QHttpNetworkRequest;
1363 const QNR::Priority ranks[] = {QNR::HighPriority,
1364 QNR::NormalPriority,
1365 QNR::LowPriority};
1366
1367 for (const QNR::Priority rank : ranks) {
1368 auto &queue = suspendedStreams[rank];
1369 auto it = queue.begin();
1370 for (; it != queue.end(); ++it) {
1371 if (!activeStreams.contains(*it))
1372 continue;
1373 if (activeStreams[*it].sendWindow > 0)
1374 break;
1375 }
1376
1377 if (it != queue.end()) {
1378 streamID = *it;
1379 queue.erase(it);
1380 break;
1381 }
1382 }
1383
1384 return streamID;
1385}
1386
1387void QHttp2ProtocolHandler::removeFromSuspended(quint32 streamID)
1388{
1389 for (auto &q : suspendedStreams) {
1390 q.erase(std::remove(q.begin(), q.end(), streamID), q.end());
1391 }
1392}
1393
1394void QHttp2ProtocolHandler::deleteActiveStream(quint32 streamID)
1395{
1396 if (activeStreams.contains(streamID)) {
1397 auto &stream = activeStreams[streamID];
1398 if (stream.reply()) {
1399 stream.reply()->disconnect(this);
1400 streamIDs.remove(stream.reply());
1401 }
1402 if (stream.data()) {
1403 stream.data()->disconnect(this);
1404 streamIDs.remove(stream.data());
1405 }
1406 activeStreams.remove(streamID);
1407 }
1408
1409 removeFromSuspended(streamID);
1410 if (m_channel->h2RequestsToSend.size())
1411 QMetaObject::invokeMethod(this, "sendRequest", Qt::QueuedConnection);
1412}
1413
1414bool QHttp2ProtocolHandler::streamWasReset(quint32 streamID) const
1415{
1416 const auto it = std::lower_bound(recycledStreams.begin(),
1417 recycledStreams.end(),
1418 streamID);
1419 return it != recycledStreams.end() && *it == streamID;
1420}
1421
1422void QHttp2ProtocolHandler::resumeSuspendedStreams()
1423{
1424 while (sessionSendWindowSize > 0) {
1425 const auto streamID = popStreamToResume();
1426 if (!streamID)
1427 return;
1428
1429 if (!activeStreams.contains(streamID))
1430 continue;
1431
1432 Stream &stream = activeStreams[streamID];
1433 if (!sendDATA(stream)) {
1434 finishStreamWithError(stream, QNetworkReply::UnknownNetworkError,
1435 QLatin1String("failed to send DATA"));
1436 sendRST_STREAM(streamID, INTERNAL_ERROR);
1437 markAsReset(streamID);
1438 deleteActiveStream(streamID);
1439 }
1440 }
1441}
1442
1443quint32 QHttp2ProtocolHandler::allocateStreamID()
1444{
1445 // With protocol upgrade streamID == 1 will become
1446 // invalid. The logic must be updated.
1447 if (nextID > Http2::lastValidStreamID)
1448 return 0;
1449
1450 const quint32 streamID = nextID;
1451 nextID += 2;
1452
1453 return streamID;
1454}
1455
1456bool QHttp2ProtocolHandler::tryReserveStream(const Http2::Frame &pushPromiseFrame,
1457 const HPack::HttpHeader &requestHeader)
1458{
1459 Q_ASSERT(pushPromiseFrame.type() == FrameType::PUSH_PROMISE);
1460
1461 QMap<QByteArray, QByteArray> pseudoHeaders;
1462 for (const auto &field : requestHeader) {
1463 if (field.name == ":scheme" || field.name == ":path"
1464 || field.name == ":authority" || field.name == ":method") {
1465 if (field.value.isEmpty() || pseudoHeaders.contains(field.name))
1466 return false;
1467 pseudoHeaders[field.name] = field.value;
1468 }
1469 }
1470
1471 if (pseudoHeaders.size() != 4) {
1472 // All four required, HTTP/2 8.1.2.3.
1473 return false;
1474 }
1475
1476 const QByteArray method = pseudoHeaders[":method"];
1477 if (method.compare("get", Qt::CaseInsensitive) != 0 &&
1478 method.compare("head", Qt::CaseInsensitive) != 0)
1479 return false;
1480
1481 QUrl url;
1482 url.setScheme(QLatin1String(pseudoHeaders[":scheme"]));
1483 url.setAuthority(QLatin1String(pseudoHeaders[":authority"]));
1484 url.setPath(QLatin1String(pseudoHeaders[":path"]));
1485
1486 if (!url.isValid())
1487 return false;
1488
1489 Q_ASSERT(activeStreams.contains(pushPromiseFrame.streamID()));
1490 const Stream &associatedStream = activeStreams[pushPromiseFrame.streamID()];
1491
1492 const auto associatedUrl = urlkey_from_request(associatedStream.request());
1493 if (url.adjusted(QUrl::RemovePath) != associatedUrl.adjusted(QUrl::RemovePath))
1494 return false;
1495
1496 const auto urlKey = url.toString();
1497 if (promisedData.contains(urlKey)) // duplicate push promise
1498 return false;
1499
1500 const auto reservedID = qFromBigEndian<quint32>(pushPromiseFrame.dataBegin());
1501 // By this time all sanity checks on reservedID were done already
1502 // in handlePUSH_PROMISE. We do not repeat them, only those below:
1503 Q_ASSERT(!activeStreams.contains(reservedID));
1504 Q_ASSERT(!streamWasReset(reservedID));
1505
1506 auto &promise = promisedData[urlKey];
1507 promise.reservedID = reservedID;
1508 promise.pushHeader = requestHeader;
1509
1510 activeStreams.insert(reservedID, Stream(urlKey, reservedID, streamInitialReceiveWindowSize));
1511 return true;
1512}
1513
1514void QHttp2ProtocolHandler::resetPromisedStream(const Frame &pushPromiseFrame,
1515 Http2::Http2Error reason)
1516{
1517 Q_ASSERT(pushPromiseFrame.type() == FrameType::PUSH_PROMISE);
1518 const auto reservedID = qFromBigEndian<quint32>(pushPromiseFrame.dataBegin());
1519 sendRST_STREAM(reservedID, reason);
1520 markAsReset(reservedID);
1521}
1522
1523void QHttp2ProtocolHandler::initReplyFromPushPromise(const HttpMessagePair &message,
1524 const QString &cacheKey)
1525{
1526 Q_ASSERT(promisedData.contains(cacheKey));
1527 auto promise = promisedData.take(cacheKey);
1528 Q_ASSERT(message.second);
1529 message.second->setHttp2WasUsed(true);
1530
1531 qCDebug(QT_HTTP2) << "found cached/promised response on stream" << promise.reservedID;
1532
1533 bool replyFinished = false;
1534 Stream *promisedStream = nullptr;
1535 if (activeStreams.contains(promise.reservedID)) {
1536 promisedStream = &activeStreams[promise.reservedID];
1537 // Ok, we have an active (not closed yet) stream waiting for more frames,
1538 // let's pretend we requested it:
1539 promisedStream->httpPair = message;
1540 } else {
1541 // Let's pretent we're sending a request now:
1542 Stream closedStream(message, promise.reservedID,
1543 streamInitialSendWindowSize,
1544 streamInitialReceiveWindowSize);
1545 closedStream.state = Stream::halfClosedLocal;
1546 activeStreams.insert(promise.reservedID, closedStream);
1547 promisedStream = &activeStreams[promise.reservedID];
1548 replyFinished = true;
1549 }
1550
1551 Q_ASSERT(promisedStream);
1552
1553 if (!promise.responseHeader.empty())
1554 updateStream(*promisedStream, promise.responseHeader, Qt::QueuedConnection);
1555
1556 for (const auto &frame : promise.dataFrames)
1557 updateStream(*promisedStream, frame, Qt::QueuedConnection);
1558
1559 if (replyFinished) {
1560 // Good, we already have received ALL the frames of that PUSH_PROMISE,
1561 // nothing more to do.
1562 finishStream(*promisedStream, Qt::QueuedConnection);
1563 deleteActiveStream(promisedStream->streamID);
1564 }
1565}
1566
1567void QHttp2ProtocolHandler::connectionError(Http2::Http2Error errorCode,
1568 const char *message)
1569{
1570 Q_ASSERT(message);
1571 Q_ASSERT(!goingAway);
1572
1573 qCCritical(QT_HTTP2) << "connection error:" << message;
1574
1575 goingAway = true;
1576 sendGOAWAY(errorCode);
1577 const auto error = qt_error(errorCode);
1578 m_channel->emitFinishedWithError(error, message);
1579
1580 for (auto &stream: activeStreams)
1581 finishStreamWithError(stream, error, QLatin1String(message));
1582
1583 closeSession();
1584}
1585
1586void QHttp2ProtocolHandler::closeSession()
1587{
1588 activeStreams.clear();
1589 for (auto &q: suspendedStreams)
1590 q.clear();
1591 recycledStreams.clear();
1592
1593 m_channel->close();
1594}
1595
1596QT_END_NAMESPACE
1597