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