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