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
4#include "qhttpnetworkconnection_p.h"
5#include "qhttp2protocolhandler_p.h"
6
7#include "http2/http2frames_p.h"
8#include "http2/bitstreams_p.h"
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
33QT_BEGIN_NAMESPACE
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
45 HttpHeader header;
46 header.reserve(n: 300);
47
48 // 1. Before anything - mandatory fields, if they do not fit into maxHeaderList -
49 // then stop immediately with error.
50 const auto auth = request.url().authority(options: QUrl::FullyEncoded | QUrl::RemoveUserInfo).toLatin1();
51 header.emplace_back(args: ":authority", args: auth);
52 header.emplace_back(args: ":method", args: request.methodName());
53 header.emplace_back(args: ":path", args: request.uri(throughProxy: useProxy));
54 header.emplace_back(args: ":scheme", args: request.url().scheme().toLatin1());
55
56 HeaderSize size = header_size(header);
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(args: QByteArray{name.data(), name.size()},
86 args: QByteArray{value.data(), value.size()});
87 }
88
89 return header;
90}
91
92QUrl urlkey_from_request(const QHttpNetworkRequest &request)
93{
94 QUrl url;
95
96 url.setScheme(request.url().scheme());
97 url.setAuthority(authority: request.url().authority(options: QUrl::FullyEncoded | QUrl::RemoveUserInfo));
98 url.setPath(path: QLatin1StringView(request.uri(throughProxy: false)));
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
108QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *channel)
109 : QAbstractProtocolHandler(channel),
110 decoder(HPack::FieldLookupTable::DefaultSize),
111 encoder(HPack::FieldLookupTable::DefaultSize, true)
112{
113 Q_ASSERT(channel && m_connection);
114 continuedFrames.reserve(n: 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
122 if (!channel->ssl && m_connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
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(message: HttpMessagePair(channel->request, channel->reply),
129 uploadDone: true /* uploaded by HTTP/1.1 */);
130 Q_ASSERT(initialStreamID == 1);
131 Stream &stream = activeStreams[initialStreamID];
132 stream.state = Stream::halfClosedLocal;
133 }
134}
135
136void QHttp2ProtocolHandler::handleConnectionClosure()
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?
144 _q_receiveReply();
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(context: "QHttp", key: "Connection closed");
151 for (auto it = activeStreams.begin(), eIt = activeStreams.end(); it != eIt; ++it)
152 finishStreamWithError(stream&: it.value(), error: QNetworkReply::RemoteHostClosedError, message: errorString);
153
154 // Make sure we'll never try to read anything later:
155 activeStreams.clear();
156 goingAway = true;
157}
158
159void QHttp2ProtocolHandler::ensureClientPrefaceSent()
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 *>(object: sender());
171 Q_ASSERT(data);
172 const qint32 streamID = streamIDs.value(key: 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, error: QNetworkReply::UnknownNetworkError, message: "failed to send DATA"_L1);
179 sendRST_STREAM(streamID, errorCoder: INTERNAL_ERROR);
180 markAsReset(streamID);
181 deleteActiveStream(streamID);
182 }
183}
184
185void QHttp2ProtocolHandler::_q_replyDestroyed(QObject *reply)
186{
187 const quint32 streamID = streamIDs.take(key: reply);
188 if (activeStreams.contains(key: streamID)) {
189 sendRST_STREAM(streamID, errorCoder: CANCEL);
190 markAsReset(streamID);
191 deleteActiveStream(streamID);
192 }
193}
194
195void QHttp2ProtocolHandler::_q_uploadDataDestroyed(QObject *uploadData)
196{
197 streamIDs.remove(key: uploadData);
198}
199
200void QHttp2ProtocolHandler::_q_readyRead()
201{
202 if (!goingAway || activeStreams.size())
203 _q_receiveReply();
204}
205
206void QHttp2ProtocolHandler::_q_receiveReply()
207{
208 Q_ASSERT(m_socket);
209 Q_ASSERT(m_channel);
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(socket&: *m_socket);
218 switch (result) {
219 case FrameStatus::incompleteFrame:
220 return;
221 case FrameStatus::protocolError:
222 return connectionError(errorCode: PROTOCOL_ERROR, message: "invalid frame");
223 case FrameStatus::sizeError:
224 return connectionError(errorCode: FRAME_SIZE_ERROR, message: "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(errorCode: PROTOCOL_ERROR, message: "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
275bool QHttp2ProtocolHandler::sendRequest()
276{
277 if (goingAway) {
278 // Stop further calls to this method: we have received GOAWAY
279 // so we cannot create new streams.
280 m_channel->emitFinishedWithError(error: QNetworkReply::ProtocolUnknownError,
281 message: "GOAWAY received, cannot start a request");
282 m_channel->h2RequestsToSend.clear();
283 return false;
284 }
285
286 // Process 'fake' (created by QNetworkAccessManager::connectToHostEncrypted())
287 // requests first:
288 auto &requests = m_channel->h2RequestsToSend;
289 for (auto it = requests.begin(), endIt = requests.end(); it != endIt;) {
290 const auto &pair = *it;
291 if (pair.first.isPreConnect()) {
292 m_connection->preConnectFinished();
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 (!requests.size())
308 return true;
309
310 if (!prefaceSent && !sendClientPreface())
311 return false;
312
313 m_channel->state = QHttpNetworkConnectionChannel::WritingState;
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(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:
324 HttpMessagePair message = *it;
325 it = requests.erase(it);
326 initReplyFromPushPromise(message, cacheKey: key);
327 }
328
329 const auto isClientSide = [](const auto &pair) -> bool { return (pair.first & 1) == 1; };
330 const auto activeClientSideStreams = std::count_if(
331 first: activeStreams.constKeyValueBegin(), last: activeStreams.constKeyValueEnd(), pred: isClientSide);
332 const qint64 streamsToUse = qBound(min: 0, val: qint64(maxConcurrentStreams) - activeClientSideStreams,
333 max: requests.size());
334 auto it = requests.begin();
335 for (qint64 i = 0; i < streamsToUse; ++i) {
336 const qint32 newStreamID = createNewStream(message: *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(stream&: newStream)) {
347 finishStreamWithError(stream&: newStream, error: QNetworkReply::UnknownNetworkError,
348 message: "failed to send HEADERS frame(s)"_L1);
349 deleteActiveStream(streamID: newStreamID);
350 continue;
351 }
352
353 if (newStream.data() && !sendDATA(stream&: newStream)) {
354 finishStreamWithError(stream&: newStream, error: QNetworkReply::UnknownNetworkError,
355 message: "failed to send DATA frame(s)"_L1);
356 sendRST_STREAM(streamID: newStreamID, errorCoder: INTERNAL_ERROR);
357 markAsReset(streamID: newStreamID);
358 deleteActiveStream(streamID: newStreamID);
359 }
360 }
361
362 m_channel->state = QHttpNetworkConnectionChannel::IdleState;
363
364 return true;
365}
366
367
368bool QHttp2ProtocolHandler::sendClientPreface()
369{
370 // 3.5 HTTP/2 Connection Preface
371 Q_ASSERT(m_socket);
372
373 if (prefaceSent)
374 return true;
375
376 const qint64 written = m_socket->write(data: Http2::Http2clientPreface,
377 len: Http2::clientPrefaceLength);
378 if (written != Http2::clientPrefaceLength)
379 return false;
380
381 // 6.5 SETTINGS
382 frameWriter.setOutboundFrame(Http2::configurationToSettingsFrame(configuration: m_connection->http2Parameters()));
383 Q_ASSERT(frameWriter.outboundFrame().payloadSize());
384
385 if (!frameWriter.write(socket&: *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(streamID: Http2::connectionStreamID, delta))
393 return false;
394
395 prefaceSent = true;
396 waitingForSettingsACK = true;
397
398 return true;
399}
400
401bool QHttp2ProtocolHandler::sendSETTINGS_ACK()
402{
403 Q_ASSERT(m_socket);
404
405 if (!prefaceSent && !sendClientPreface())
406 return false;
407
408 frameWriter.start(type: FrameType::SETTINGS, flags: FrameFlag::ACK, streamID: Http2::connectionStreamID);
409
410 return frameWriter.write(socket&: *m_socket);
411}
412
413bool QHttp2ProtocolHandler::sendHEADERS(Stream &stream)
414{
415 using namespace HPack;
416
417 frameWriter.start(type: FrameType::HEADERS, flags: FrameFlag::PRIORITY | FrameFlag::END_HEADERS,
418 streamID: stream.streamID);
419
420 if (!stream.data()) {
421 frameWriter.addFlag(flag: FrameFlag::END_STREAM);
422 stream.state = Stream::halfClosedLocal;
423 } else {
424 stream.state = Stream::open;
425 }
426
427 frameWriter.append(val: quint32()); // No stream dependency in Qt.
428 frameWriter.append(val: 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(socket: m_socket, request&: stream.request());
436 stream.request().d->needResendWithCredentials = false;
437 }
438 const auto headers = build_headers(request: 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, header: headers))
445 return false;
446
447 return frameWriter.writeHEADERS(socket&: *m_socket, sizeLimit: maxFrameSize);
448}
449
450bool QHttp2ProtocolHandler::sendDATA(Stream &stream)
451{
452 Q_ASSERT(maxFrameSize > frameHeaderSize);
453 Q_ASSERT(m_socket);
454 Q_ASSERT(stream.data());
455
456 const auto &request = stream.request();
457 auto reply = stream.reply();
458 Q_ASSERT(reply);
459 const auto replyPrivate = reply->d_func();
460 Q_ASSERT(replyPrivate);
461
462 auto slot = std::min<qint32>(a: sessionSendWindowSize, b: 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(maximumLength: slot, len&: 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(type: FrameType::DATA, flags: FrameFlag::EMPTY, streamID: stream.streamID);
478 const qint32 bytesWritten = qint32(std::min<qint64>(a: slot, b: chunkSize));
479
480 if (!frameWriter.writeDATA(socket&: *m_socket, sizeLimit: maxFrameSize, src, size: bytesWritten))
481 return false;
482
483 stream.data()->advanceReadPointer(amount: bytesWritten);
484 stream.sendWindow -= bytesWritten;
485 sessionSendWindowSize -= bytesWritten;
486 replyPrivate->totallyUploadedData += bytesWritten;
487 emit reply->dataSendProgress(done: replyPrivate->totallyUploadedData,
488 total: request.contentLength());
489 slot = std::min(a: sessionSendWindowSize, b: stream.sendWindow);
490 }
491
492 if (replyPrivate->totallyUploadedData == request.contentLength()) {
493 frameWriter.start(type: FrameType::DATA, flags: FrameFlag::END_STREAM, streamID: stream.streamID);
494 frameWriter.setPayloadSize(0);
495 frameWriter.write(socket&: *m_socket);
496 stream.state = Stream::halfClosedLocal;
497 stream.data()->disconnect(receiver: this);
498 removeFromSuspended(streamID: 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{
508 Q_ASSERT(m_socket);
509
510 frameWriter.start(type: FrameType::WINDOW_UPDATE, flags: FrameFlag::EMPTY, streamID);
511 frameWriter.append(val: delta);
512 return frameWriter.write(socket&: *m_socket);
513}
514
515bool QHttp2ProtocolHandler::sendRST_STREAM(quint32 streamID, quint32 errorCode)
516{
517 Q_ASSERT(m_socket);
518
519 frameWriter.start(type: FrameType::RST_STREAM, flags: FrameFlag::EMPTY, streamID);
520 frameWriter.append(val: errorCode);
521 return frameWriter.write(socket&: *m_socket);
522}
523
524bool QHttp2ProtocolHandler::sendGOAWAY(quint32 errorCode)
525{
526 Q_ASSERT(m_socket);
527
528 frameWriter.start(type: FrameType::GOAWAY, flags: FrameFlag::EMPTY, streamID: connectionStreamID);
529 frameWriter.append(val: quint32(connectionStreamID));
530 frameWriter.append(val: errorCode);
531 return frameWriter.write(socket&: *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(errorCode: PROTOCOL_ERROR, message: "DATA on stream 0x0");
541
542 if (!activeStreams.contains(key: streamID) && !streamWasReset(streamID))
543 return connectionError(errorCode: ENHANCE_YOUR_CALM, message: "DATA on invalid stream");
544
545 if (qint32(inboundFrame.payloadSize()) > sessionReceiveWindowSize)
546 return connectionError(errorCode: FLOW_CONTROL_ERROR, message: "Flow control error");
547
548 sessionReceiveWindowSize -= inboundFrame.payloadSize();
549
550 auto it = activeStreams.find(key: streamID);
551 if (it != activeStreams.end()) {
552 Stream &stream = it.value();
553
554 if (qint32(inboundFrame.payloadSize()) > stream.recvWindow) {
555 finishStreamWithError(stream, error: QNetworkReply::ProtocolFailure, message: "flow control error"_L1);
556 sendRST_STREAM(streamID, errorCode: 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, dataFrame: inboundFrame);
563
564 if (inboundFrame.flags().testFlag(flag: FrameFlag::END_STREAM)) {
565 finishStream(stream);
566 deleteActiveStream(streamID: stream.streamID);
567 } else if (stream.recvWindow < streamInitialReceiveWindowSize / 2) {
568 QMetaObject::invokeMethod(obj: this, member: "sendWINDOW_UPDATE", c: 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(obj: this, member: "sendWINDOW_UPDATE", c: Qt::QueuedConnection,
578 Q_ARG(quint32, connectionStreamID),
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(errorCode: PROTOCOL_ERROR, message: "HEADERS on 0x0 stream");
591
592 if (!activeStreams.contains(key: streamID) && !streamWasReset(streamID))
593 return connectionError(errorCode: ENHANCE_YOUR_CALM, message: "HEADERS on invalid stream");
594
595 const auto flags = inboundFrame.flags();
596 if (flags.testFlag(flag: FrameFlag::PRIORITY)) {
597 handlePRIORITY();
598 if (goingAway)
599 return;
600 }
601
602 const bool endHeaders = flags.testFlag(flag: FrameFlag::END_HEADERS);
603 continuedFrames.clear();
604 continuedFrames.push_back(x: 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(errorCode: PROTOCOL_ERROR, message: "PIRORITY on 0x0 stream");
621
622 if (!activeStreams.contains(key: streamID) && !streamWasReset(streamID))
623 return connectionError(errorCode: ENHANCE_YOUR_CALM, message: "PRIORITY on invalid stream");
624
625 quint32 streamDependency = 0;
626 uchar weight = 0;
627 const bool noErr = inboundFrame.priority(streamID: &streamDependency, weight: &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);
638 Q_UNUSED(weight);
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(errorCode: PROTOCOL_ERROR, message: "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(errorCode: PROTOCOL_ERROR, message: "RST_STREAM on idle stream");
665 }
666
667 if (!activeStreams.contains(key: streamID)) {
668 // 'closed' stream, ignore.
669 return;
670 }
671
672 Q_ASSERT(inboundFrame.dataSize() == 4);
673
674 Stream &stream = activeStreams[streamID];
675 finishStreamWithError(stream, errorCode: qFromBigEndian<quint32>(src: inboundFrame.dataBegin()));
676 markAsReset(streamID: stream.streamID);
677 deleteActiveStream(streamID: 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(errorCode: PROTOCOL_ERROR, message: "SETTINGS on invalid stream");
687
688 if (inboundFrame.flags().testFlag(flag: FrameFlag::ACK)) {
689 if (!waitingForSettingsACK)
690 return connectionError(errorCode: PROTOCOL_ERROR, message: "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: src + 2);
700 if (!acceptSetting(identifier, newValue: 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(errorCode: PROTOCOL_ERROR, message: "unexpected PUSH_PROMISE frame");
720 }
721
722 const auto streamID = inboundFrame.streamID();
723 if (streamID == connectionStreamID) {
724 return connectionError(errorCode: PROTOCOL_ERROR,
725 message: "PUSH_PROMISE with invalid associated stream (0x0)");
726 }
727
728 if (!activeStreams.contains(key: streamID) && !streamWasReset(streamID)) {
729 return connectionError(errorCode: ENHANCE_YOUR_CALM,
730 message: "PUSH_PROMISE with invalid associated stream");
731 }
732
733 const auto reservedID = qFromBigEndian<quint32>(src: inboundFrame.dataBegin());
734 if ((reservedID & 1) || reservedID <= lastPromisedID ||
735 reservedID > Http2::lastValidStreamID) {
736 return connectionError(errorCode: PROTOCOL_ERROR,
737 message: "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(pushPromiseFrame: inboundFrame, reason: Http2::REFUSE_STREAM);
746 }
747
748 const bool endHeaders = inboundFrame.flags().testFlag(flag: FrameFlag::END_HEADERS);
749 continuedFrames.clear();
750 continuedFrames.push_back(x: 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);
765 Q_ASSERT(m_socket);
766
767 if (inboundFrame.streamID() != connectionStreamID)
768 return connectionError(errorCode: PROTOCOL_ERROR, message: "PING on invalid stream");
769
770 if (inboundFrame.flags() & FrameFlag::ACK)
771 return connectionError(errorCode: PROTOCOL_ERROR, message: "unexpected PING ACK");
772
773 Q_ASSERT(inboundFrame.dataSize() == 8);
774
775 frameWriter.start(type: FrameType::PING, flags: FrameFlag::ACK, streamID: connectionStreamID);
776 frameWriter.append(begin: inboundFrame.dataBegin(), end: inboundFrame.dataBegin() + 8);
777 frameWriter.write(socket&: *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(errorCode: PROTOCOL_ERROR, message: "GOAWAY on invalid stream");
789
790 const auto src = inboundFrame.dataBegin();
791 quint32 lastStreamID = qFromBigEndian<quint32>(src);
792 const quint32 errorCode = qFromBigEndian<quint32>(src: 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(errorCode: PROTOCOL_ERROR, message: "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(errorCode: PROTOCOL_ERROR, message: "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.
815 m_channel->emitFinishedWithError(error: QNetworkReply::ProtocolUnknownError,
816 message: "GOAWAY received, cannot start a request");
817 // Also, prevent further calls to sendRequest:
818 m_channel->h2RequestsToSend.clear();
819
820 QNetworkReply::NetworkError error = QNetworkReply::NoError;
821 QString message;
822 qt_error(errorCode, error, errorString&: 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) {
828 error = QNetworkReply::ContentReSendError;
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(key: id);
834 if (it != activeStreams.end()) {
835 Stream &stream = *it;
836 finishStreamWithError(stream, error, message);
837 markAsReset(streamID: id);
838 deleteActiveStream(streamID: id);
839 } else {
840 removeFromSuspended(streamID: 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>(src: 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(v1: sessionSendWindowSize, v2: qint32(delta), r: &sum))
860 return connectionError(errorCode: PROTOCOL_ERROR, message: "WINDOW_UPDATE invalid delta");
861 sessionSendWindowSize = sum;
862 } else {
863 auto it = activeStreams.find(key: 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(v1: stream.sendWindow, v2: qint32(delta), r: &sum)) {
871 finishStreamWithError(stream, error: QNetworkReply::ProtocolFailure,
872 message: "invalid WINDOW_UPDATE delta"_L1);
873 sendRST_STREAM(streamID, errorCode: 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(obj: this, member: "resumeSuspendedStreams", c: 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(errorCode: PROTOCOL_ERROR, message: "CONTINUATION on invalid stream");
894
895 const bool endHeaders = inboundFrame.flags().testFlag(flag: FrameFlag::END_HEADERS);
896 continuedFrames.push_back(x: 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(key: streamID);
918 if (firstFrameType == FrameType::HEADERS) {
919 if (streamIt != activeStreams.end()) {
920 Stream &stream = streamIt.value();
921 if (stream.state != Stream::halfClosedLocal
922 && stream.state != Stream::remoteReserved
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, error: QNetworkReply::ProtocolFailure,
928 message: "HEADERS on invalid stream"_L1);
929 sendRST_STREAM(streamID, errorCode: CANCEL);
930 markAsReset(streamID);
931 deleteActiveStream(streamID);
932 return;
933 }
934 } else if (!streamWasReset(streamID)) {
935 return connectionError(errorCode: PROTOCOL_ERROR, message: "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(frames: 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(errorCode: COMPRESSION_ERROR, message: "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(pushPromiseFrame: continuedFrames[0], reason: 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, headers: 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(streamID: stream.streamID);
973 }
974 }
975 break;
976 case FrameType::PUSH_PROMISE:
977 if (!tryReserveStream(pushPromiseFrame: continuedFrames[0], requestHeader: decoder.decodedHeader()))
978 resetPromisedStream(pushPromiseFrame: continuedFrames[0], reason: 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(errorCode: PROTOCOL_ERROR, message: "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(errorCode: FLOW_CONTROL_ERROR, message: "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(n: activeStreams.size());
1008 for (auto &stream : activeStreams) {
1009 qint32 sum = 0;
1010 if (qAddOverflow(v1: stream.sendWindow, v2: delta, r: &sum)) {
1011 brokenStreams.push_back(x: stream.streamID);
1012 continue;
1013 }
1014 stream.sendWindow = sum;
1015 }
1016
1017 for (auto id : brokenStreams) {
1018 auto &stream = activeStreams[id];
1019 finishStreamWithError(stream, error: QNetworkReply::ProtocolFailure,
1020 message: "SETTINGS window overflow"_L1);
1021 sendRST_STREAM(streamID: id, errorCode: PROTOCOL_ERROR);
1022 markAsReset(streamID: id);
1023 deleteActiveStream(streamID: id);
1024 }
1025
1026 QMetaObject::invokeMethod(obj: this, member: "resumeSuspendedStreams", c: 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(errorCode: PROTOCOL_ERROR, message: "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(n: 3).toInt();
1089 httpReply->setStatusCode(statusCode);
1090 m_channel->lastStatus = statusCode; // Mostly useless for http/2, needed for auth
1091 httpReply->setReasonPhrase(QString::fromLatin1(ba: value.mid(pos: 4)));
1092 } else if (name == ":version") {
1093 httpReply->setMajorVersion(value.at(n: 5) - '0');
1094 httpReply->setMinorVersion(value.at(n: 7) - '0');
1095 } else if (name == "content-length") {
1096 bool ok = false;
1097 const qlonglong length = value.toLongLong(ok: &ok);
1098 if (ok)
1099 httpReply->setContentLength(length);
1100 } else {
1101 const auto binder = name == "set-cookie" ? QByteArrayView("\n") : QByteArrayView(", ");
1102 httpReply->appendHeaderField(name, data: QByteArray(pair.value).replace(before: '\0', after: 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()) {
1114 QHttpNetworkConnectionPrivate::ParseRedirectResult result =
1115 m_connection->d_func()->parseRedirectResponse(reply: httpReply);
1116 if (result.errorCode != QNetworkReply::NoError) {
1117 auto errorString = m_connection->d_func()->errorDetail(errorCode: result.errorCode, socket: m_socket);
1118 finishStreamWithError(stream, error: result.errorCode, message: errorString);
1119 sendRST_STREAM(streamID: stream.streamID, errorCode: INTERNAL_ERROR);
1120 markAsReset(streamID: 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(obj: httpReply, member: "headerChanged", c: 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(x: 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(bd: QByteArray(data, length));
1171
1172 if (replyPrivate->shouldEmitSignals()) {
1173 if (connectionType == Qt::DirectConnection) {
1174 emit httpReply->readyRead();
1175 emit httpReply->dataReadProgress(done: replyPrivate->totalProgress,
1176 total: replyPrivate->bodyLength);
1177 } else {
1178 QMetaObject::invokeMethod(obj: httpReply, member: "readyRead", c: connectionType);
1179 QMetaObject::invokeMethod(obj: httpReply, member: "dataReadProgress", c: 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(other: "Negotiate") || auth.startsWith(other: "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 socket: m_socket, reply: 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(key: httpRequest.priority(), value: 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
1242 ? QNetworkReply::AuthenticationRequiredError
1243 : QNetworkReply::ProxyAuthenticationRequiredError;
1244 finishStreamWithError(stream, error: QNetworkReply::AuthenticationRequiredError,
1245 message: m_connection->d_func()->errorDetail(errorCode: error, socket: 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(name: "www-authenticate"), false);
1258 break;
1259 case 407:
1260 authOk = handleAuth(httpReply->headerField(name: "proxy-authenticate"), true);
1261 break;
1262 default:
1263 Q_UNREACHABLE();
1264 }
1265 if (authOk) {
1266 markAsReset(streamID: stream.streamID);
1267 deleteActiveStream(streamID: 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(receiver: this);
1289 if (stream.data())
1290 stream.data()->disconnect(receiver: this);
1291
1292 if (!stream.request().d->needResendWithCredentials) {
1293 if (connectionType == Qt::DirectConnection)
1294 emit httpReply->finished();
1295 else
1296 QMetaObject::invokeMethod(obj: httpReply, member: "finished", c: connectionType);
1297 }
1298 }
1299
1300 qCDebug(QT_HTTP2) << "stream" << stream.streamID << "closed";
1301}
1302
1303void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, quint32 errorCode)
1304{
1305 QNetworkReply::NetworkError error = QNetworkReply::NoError;
1306 QString message;
1307 qt_error(errorCode, error, errorString&: 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(receiver: this);
1319 if (stream.data())
1320 stream.data()->disconnect(receiver: this);
1321
1322 // TODO: error message must be translated!!! (tr)
1323 emit httpReply->finishedWithError(errorCode: error, detail: 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(key: reply, value: newStreamID);
1344 connect(sender: reply, SIGNAL(destroyed(QObject*)),
1345 receiver: 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(sender: src, SIGNAL(readyRead()), receiver: this,
1354 SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection);
1355 connect(sender: src, signal: &QHttp2ProtocolHandler::destroyed,
1356 context: this, slot: &QHttp2ProtocolHandler::_q_uploadDataDestroyed);
1357 streamIDs.insert(key: src, value: newStreamID);
1358 }
1359 }
1360
1361 QMetaObject::invokeMethod(obj: reply, member: "requestSent", c: Qt::QueuedConnection);
1362
1363 activeStreams.insert(key: newStreamID, value: 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(x: 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(first: recycledStreams.begin(),
1387 last: recycledStreams.begin() +
1388 recycledStreams.size() / 2);
1389 }
1390
1391 const auto it = std::lower_bound(first: recycledStreams.begin(), last: recycledStreams.end(),
1392 val: streamID);
1393 if (it != recycledStreams.end() && *it == streamID)
1394 return;
1395
1396 recycledStreams.insert(position: it, x: 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(key: *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(position: 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(first: std::remove(first: q.begin(), last: q.end(), value: streamID), last: q.end());
1432 }
1433}
1434
1435void QHttp2ProtocolHandler::deleteActiveStream(quint32 streamID)
1436{
1437 if (const auto it = activeStreams.constFind(key: streamID); it != activeStreams.cend()) {
1438 const Stream &stream = it.value();
1439 if (stream.reply()) {
1440 stream.reply()->disconnect(receiver: this);
1441 streamIDs.remove(key: stream.reply());
1442 }
1443 if (stream.data()) {
1444 stream.data()->disconnect(receiver: this);
1445 streamIDs.remove(key: stream.data());
1446 }
1447 activeStreams.erase(it);
1448 }
1449
1450 removeFromSuspended(streamID);
1451 if (m_channel->h2RequestsToSend.size())
1452 QMetaObject::invokeMethod(obj: this, member: "sendRequest", c: Qt::QueuedConnection);
1453}
1454
1455bool QHttp2ProtocolHandler::streamWasReset(quint32 streamID) const
1456{
1457 const auto it = std::lower_bound(first: recycledStreams.begin(),
1458 last: recycledStreams.end(),
1459 val: 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(key: streamID);
1471 if (it == activeStreams.end())
1472 continue;
1473 Stream &stream = it.value();
1474
1475 if (!sendDATA(stream)) {
1476 finishStreamWithError(stream, error: QNetworkReply::UnknownNetworkError,
1477 message: "failed to send DATA"_L1);
1478 sendRST_STREAM(streamID, errorCode: 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(request: associatedStream.request());
1511 if (url->adjusted(options: QUrl::RemovePath) != associatedUrl.adjusted(options: QUrl::RemovePath))
1512 return false;
1513
1514 const auto urlKey = url->toString();
1515 if (promisedData.contains(key: urlKey)) // duplicate push promise
1516 return false;
1517
1518 const auto reservedID = qFromBigEndian<quint32>(src: 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(key: reservedID, value: 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>(src: pushPromiseFrame.dataBegin());
1537 sendRST_STREAM(streamID: reservedID, errorCode: reason);
1538 markAsReset(streamID: 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(key: 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(key: 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(key: promise.reservedID, value: closedStream);
1565 promisedStream = &it.value();
1566 replyFinished = true;
1567 }
1568
1569 Q_ASSERT(promisedStream);
1570
1571 if (!promise.responseHeader.empty())
1572 updateStream(stream&: *promisedStream, headers: promise.responseHeader, connectionType: Qt::QueuedConnection);
1573
1574 for (const auto &frame : promise.dataFrames)
1575 updateStream(stream&: *promisedStream, frame, connectionType: 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(stream&: *promisedStream, connectionType: Qt::QueuedConnection);
1581 deleteActiveStream(streamID: promisedStream->streamID);
1582 }
1583}
1584
1585void QHttp2ProtocolHandler::connectionError(Http2::Http2Error errorCode,
1586 const char *message)
1587{
1588 Q_ASSERT(message);
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);
1596 m_channel->emitFinishedWithError(error, message);
1597
1598 for (auto &stream: activeStreams)
1599 finishStreamWithError(stream, error, message: 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
1614QT_END_NAMESPACE
1615
1616#include "moc_qhttp2protocolhandler_p.cpp"
1617

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/network/access/qhttp2protocolhandler.cpp