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