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