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 | |
33 | QT_BEGIN_NAMESPACE |
34 | |
35 | using namespace Qt::StringLiterals; |
36 | |
37 | namespace |
38 | { |
39 | |
40 | HPack::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 | |
92 | QUrl 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: |
106 | using namespace Http2; |
107 | |
108 | QHttp2ProtocolHandler::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 | |
136 | void 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 | |
159 | void QHttp2ProtocolHandler::ensureClientPrefaceSent() |
160 | { |
161 | if (!prefaceSent) |
162 | sendClientPreface(); |
163 | } |
164 | |
165 | void 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 | |
185 | void 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 | |
195 | void QHttp2ProtocolHandler::_q_uploadDataDestroyed(QObject *uploadData) |
196 | { |
197 | streamIDs.remove(key: uploadData); |
198 | } |
199 | |
200 | void QHttp2ProtocolHandler::_q_readyRead() |
201 | { |
202 | if (!goingAway || activeStreams.size()) |
203 | _q_receiveReply(); |
204 | } |
205 | |
206 | void 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 | |
275 | bool 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 | |
368 | bool 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 | |
401 | bool 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 | |
413 | bool 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 | |
450 | bool 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 | |
506 | bool 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 | |
515 | bool 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 | |
524 | bool 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 | |
534 | void 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 | |
584 | void 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 | |
613 | void 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 | |
641 | void 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 | |
680 | void 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 | |
711 | void 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 | |
760 | void 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 | |
780 | void 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 | |
848 | void 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 | |
887 | void 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 | |
905 | void 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 | |
985 | bool 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 | |
1050 | void 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 | |
1149 | void 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! |
1190 | void 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 |
1272 | void 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 | |
1303 | void 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 | |
1311 | void 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 | |
1330 | quint32 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 | |
1368 | void 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 | |
1377 | void 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 | |
1399 | quint32 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 | |
1428 | void 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 | |
1435 | void 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 | |
1455 | bool 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 | |
1463 | void 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 | |
1485 | quint32 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 | |
1498 | bool 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 | |
1532 | void 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 | |
1541 | void 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 | |
1585 | void 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 | |
1604 | void QHttp2ProtocolHandler::closeSession() |
1605 | { |
1606 | activeStreams.clear(); |
1607 | for (auto &q: suspendedStreams) |
1608 | q.clear(); |
1609 | recycledStreams.clear(); |
1610 | |
1611 | m_channel->close(); |
1612 | } |
1613 | |
1614 | QT_END_NAMESPACE |
1615 | |
1616 | #include "moc_qhttp2protocolhandler_p.cpp" |
1617 |
Definitions
- build_headers
- urlkey_from_request
- QHttp2ProtocolHandler
- handleConnectionClosure
- ensureClientPrefaceSent
- _q_uploadDataReadyRead
- _q_replyDestroyed
- _q_uploadDataDestroyed
- _q_readyRead
- _q_receiveReply
- sendRequest
- sendClientPreface
- sendSETTINGS_ACK
- sendHEADERS
- sendDATA
- sendWINDOW_UPDATE
- sendRST_STREAM
- sendGOAWAY
- handleDATA
- handleHEADERS
- handlePRIORITY
- handleRST_STREAM
- handleSETTINGS
- handlePUSH_PROMISE
- handlePING
- handleGOAWAY
- handleWINDOW_UPDATE
- handleCONTINUATION
- handleContinuedHEADERS
- acceptSetting
- updateStream
- updateStream
- handleAuthorization
- finishStream
- finishStreamWithError
- finishStreamWithError
- createNewStream
- addToSuspended
- markAsReset
- popStreamToResume
- removeFromSuspended
- deleteActiveStream
- streamWasReset
- resumeSuspendedStreams
- allocateStreamID
- tryReserveStream
- resetPromisedStream
- initReplyFromPushPromise
- connectionError
Learn Advanced QML with KDAB
Find out more