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

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