QtBase  v6.3.1
qhttp2protocolhandler.cpp
Go to the documentation of this file.
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 
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 
67 
68 namespace
69 {
70 
72  bool useProxy)
73 {
74  using namespace HPack;
75 
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 
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 
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 
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 
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 
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 
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];
197  }
198 }
199 
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 
224 {
225  if (!prefaceSent)
226  sendClientPreface();
227 }
228 
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 
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 
260 void QHttp2ProtocolHandler::_q_uploadDataDestroyed(QObject *uploadData)
261 {
262  streamIDs.remove(uploadData);
263 }
264 
265 void QHttp2ProtocolHandler::_q_readyRead()
266 {
267  if (!goingAway || activeStreams.size())
268  _q_receiveReply();
269 }
270 
271 void QHttp2ProtocolHandler::_q_receiveReply()
272 {
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 
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.
346  "GOAWAY received, cannot start a request");
348  return false;
349  }
350 
351  // Process 'fake' (created by QNetworkAccessManager::connectToHostEncrypted())
352  // requests first:
353  auto &requests = m_channel->h2RequestsToSend;
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")) {
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 
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:
392  it = requests.erase(it);
393  initReplyFromPushPromise(message, key);
394  }
395 
396  const auto streamsToUse = std::min<quint32>(maxConcurrentStreams > quint32(activeStreams.size())
397  ? maxConcurrentStreams - quint32(activeStreams.size()) : 0,
398  requests.size());
399  auto it = requests.begin();
400  for (quint32 i = 0; i < streamsToUse; ++i) {
401  const qint32 newStreamID = createNewStream(*it);
402  if (!newStreamID) {
403  // TODO: actually we have to open a new connection.
404  qCCritical(QT_HTTP2, "sendRequest: out of stream IDs");
405  break;
406  }
407 
408  it = requests.erase(it);
409 
410  Stream &newStream = activeStreams[newStreamID];
411  if (!sendHEADERS(newStream)) {
412  finishStreamWithError(newStream, QNetworkReply::UnknownNetworkError,
413  QLatin1String("failed to send HEADERS frame(s)"));
414  deleteActiveStream(newStreamID);
415  continue;
416  }
417 
418  if (newStream.data() && !sendDATA(newStream)) {
419  finishStreamWithError(newStream, QNetworkReply::UnknownNetworkError,
420  QLatin1String("failed to send DATA frame(s)"));
421  sendRST_STREAM(newStreamID, INTERNAL_ERROR);
422  markAsReset(newStreamID);
423  deleteActiveStream(newStreamID);
424  }
425  }
426 
428 
429  return true;
430 }
431 
432 
433 bool QHttp2ProtocolHandler::sendClientPreface()
434 {
435  // 3.5 HTTP/2 Connection Preface
437 
438  if (prefaceSent)
439  return true;
440 
443  if (written != Http2::clientPrefaceLength)
444  return false;
445 
446  // 6.5 SETTINGS
448  Q_ASSERT(frameWriter.outboundFrame().payloadSize());
449 
450  if (!frameWriter.write(*m_socket))
451  return false;
452 
453  sessionReceiveWindowSize = maxSessionReceiveWindowSize;
454  // We only send WINDOW_UPDATE for the connection if the size differs from the
455  // default 64 KB:
456  const auto delta = maxSessionReceiveWindowSize - Http2::defaultSessionWindowSize;
457  if (delta && !sendWINDOW_UPDATE(Http2::connectionStreamID, delta))
458  return false;
459 
460  prefaceSent = true;
461  waitingForSettingsACK = true;
462 
463  return true;
464 }
465 
466 bool QHttp2ProtocolHandler::sendSETTINGS_ACK()
467 {
469 
470  if (!prefaceSent && !sendClientPreface())
471  return false;
472 
473  frameWriter.start(FrameType::SETTINGS, FrameFlag::ACK, Http2::connectionStreamID);
474 
475  return frameWriter.write(*m_socket);
476 }
477 
478 bool QHttp2ProtocolHandler::sendHEADERS(Stream &stream)
479 {
480  using namespace HPack;
481 
482  frameWriter.start(FrameType::HEADERS, FrameFlag::PRIORITY | FrameFlag::END_HEADERS,
483  stream.streamID);
484 
485  if (!stream.data()) {
486  frameWriter.addFlag(FrameFlag::END_STREAM);
488  } else {
489  stream.state = Stream::open;
490  }
491 
492  frameWriter.append(quint32()); // No stream dependency in Qt.
493  frameWriter.append(stream.weight());
494 
495  bool useProxy = false;
496 #ifndef QT_NO_NETWORKPROXY
497  useProxy = m_connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy;
498 #endif
499  if (stream.request().withCredentials()) {
500  m_connection->d_func()->createAuthorization(m_socket, stream.request());
501  stream.request().d->needResendWithCredentials = false;
502  }
503  const auto headers = build_headers(stream.request(), maxHeaderListSize, useProxy);
504  if (!headers.size()) // nothing fits into maxHeaderListSize
505  return false;
506 
507  // Compress in-place:
508  BitOStream outputStream(frameWriter.outboundFrame().buffer);
509  if (!encoder.encodeRequest(outputStream, headers))
510  return false;
511 
512  return frameWriter.writeHEADERS(*m_socket, maxFrameSize);
513 }
514 
515 bool QHttp2ProtocolHandler::sendDATA(Stream &stream)
516 {
517  Q_ASSERT(maxFrameSize > frameHeaderSize);
519  Q_ASSERT(stream.data());
520 
521  const auto &request = stream.request();
522  auto reply = stream.reply();
523  Q_ASSERT(reply);
524  const auto replyPrivate = reply->d_func();
525  Q_ASSERT(replyPrivate);
526 
527  auto slot = std::min<qint32>(sessionSendWindowSize, stream.sendWindow);
528  while (replyPrivate->totallyUploadedData < request.contentLength() && slot) {
529  qint64 chunkSize = 0;
530  const uchar *src =
531  reinterpret_cast<const uchar *>(stream.data()->readPointer(slot, chunkSize));
532 
533  if (chunkSize == -1)
534  return false;
535 
536  if (!src || !chunkSize) {
537  // Stream is not suspended by the flow control,
538  // we do not have data ready yet.
539  return true;
540  }
541 
542  frameWriter.start(FrameType::DATA, FrameFlag::EMPTY, stream.streamID);
543  const qint32 bytesWritten = std::min<qint32>(slot, chunkSize);
544 
545  if (!frameWriter.writeDATA(*m_socket, maxFrameSize, src, bytesWritten))
546  return false;
547 
548  stream.data()->advanceReadPointer(bytesWritten);
549  stream.sendWindow -= bytesWritten;
550  sessionSendWindowSize -= bytesWritten;
551  replyPrivate->totallyUploadedData += bytesWritten;
552  emit reply->dataSendProgress(replyPrivate->totallyUploadedData,
553  request.contentLength());
554  slot = std::min(sessionSendWindowSize, stream.sendWindow);
555  }
556 
557  if (replyPrivate->totallyUploadedData == request.contentLength()) {
558  frameWriter.start(FrameType::DATA, FrameFlag::END_STREAM, stream.streamID);
559  frameWriter.setPayloadSize(0);
560  frameWriter.write(*m_socket);
562  stream.data()->disconnect(this);
563  removeFromSuspended(stream.streamID);
564  } else if (!stream.data()->atEnd()) {
565  addToSuspended(stream);
566  }
567 
568  return true;
569 }
570 
571 bool QHttp2ProtocolHandler::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
572 {
574 
575  frameWriter.start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID);
576  frameWriter.append(delta);
577  return frameWriter.write(*m_socket);
578 }
579 
580 bool QHttp2ProtocolHandler::sendRST_STREAM(quint32 streamID, quint32 errorCode)
581 {
583 
584  frameWriter.start(FrameType::RST_STREAM, FrameFlag::EMPTY, streamID);
585  frameWriter.append(errorCode);
586  return frameWriter.write(*m_socket);
587 }
588 
589 bool QHttp2ProtocolHandler::sendGOAWAY(quint32 errorCode)
590 {
592 
593  frameWriter.start(FrameType::GOAWAY, FrameFlag::EMPTY, connectionStreamID);
594  frameWriter.append(quint32(connectionStreamID));
595  frameWriter.append(errorCode);
596  return frameWriter.write(*m_socket);
597 }
598 
599 void QHttp2ProtocolHandler::handleDATA()
600 {
601  Q_ASSERT(inboundFrame.type() == FrameType::DATA);
602 
603  const auto streamID = inboundFrame.streamID();
604  if (streamID == connectionStreamID)
605  return connectionError(PROTOCOL_ERROR, "DATA on stream 0x0");
606 
607  if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
608  return connectionError(ENHANCE_YOUR_CALM, "DATA on invalid stream");
609 
610  if (qint32(inboundFrame.payloadSize()) > sessionReceiveWindowSize)
611  return connectionError(FLOW_CONTROL_ERROR, "Flow control error");
612 
613  sessionReceiveWindowSize -= inboundFrame.payloadSize();
614 
615  if (activeStreams.contains(streamID)) {
616  auto &stream = activeStreams[streamID];
617 
618  if (qint32(inboundFrame.payloadSize()) > stream.recvWindow) {
619  finishStreamWithError(stream, QNetworkReply::ProtocolFailure,
620  QLatin1String("flow control error"));
621  sendRST_STREAM(streamID, FLOW_CONTROL_ERROR);
622  markAsReset(streamID);
623  deleteActiveStream(streamID);
624  } else {
625  stream.recvWindow -= inboundFrame.payloadSize();
626  // Uncompress data if needed and append it ...
627  updateStream(stream, inboundFrame);
628 
629  if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) {
630  finishStream(stream);
631  deleteActiveStream(stream.streamID);
632  } else if (stream.recvWindow < streamInitialReceiveWindowSize / 2) {
633  QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection,
634  Q_ARG(quint32, stream.streamID),
635  Q_ARG(quint32, streamInitialReceiveWindowSize - stream.recvWindow));
636  stream.recvWindow = streamInitialReceiveWindowSize;
637  }
638  }
639  }
640 
641  if (sessionReceiveWindowSize < maxSessionReceiveWindowSize / 2) {
642  QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection,
644  Q_ARG(quint32, maxSessionReceiveWindowSize - sessionReceiveWindowSize));
645  sessionReceiveWindowSize = maxSessionReceiveWindowSize;
646  }
647 }
648 
649 void QHttp2ProtocolHandler::handleHEADERS()
650 {
651  Q_ASSERT(inboundFrame.type() == FrameType::HEADERS);
652 
653  const auto streamID = inboundFrame.streamID();
654  if (streamID == connectionStreamID)
655  return connectionError(PROTOCOL_ERROR, "HEADERS on 0x0 stream");
656 
657  if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
658  return connectionError(ENHANCE_YOUR_CALM, "HEADERS on invalid stream");
659 
660  const auto flags = inboundFrame.flags();
661  if (flags.testFlag(FrameFlag::PRIORITY)) {
662  handlePRIORITY();
663  if (goingAway)
664  return;
665  }
666 
667  const bool endHeaders = flags.testFlag(FrameFlag::END_HEADERS);
668  continuedFrames.clear();
669  continuedFrames.push_back(std::move(inboundFrame));
670  if (!endHeaders) {
671  continuationExpected = true;
672  return;
673  }
674 
675  handleContinuedHEADERS();
676 }
677 
678 void QHttp2ProtocolHandler::handlePRIORITY()
679 {
680  Q_ASSERT(inboundFrame.type() == FrameType::PRIORITY ||
681  inboundFrame.type() == FrameType::HEADERS);
682 
683  const auto streamID = inboundFrame.streamID();
684  if (streamID == connectionStreamID)
685  return connectionError(PROTOCOL_ERROR, "PIRORITY on 0x0 stream");
686 
687  if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
688  return connectionError(ENHANCE_YOUR_CALM, "PRIORITY on invalid stream");
689 
690  quint32 streamDependency = 0;
691  uchar weight = 0;
692  const bool noErr = inboundFrame.priority(&streamDependency, &weight);
693  Q_UNUSED(noErr);
694  Q_ASSERT(noErr);
695 
696 
697  const bool exclusive = streamDependency & 0x80000000;
698  streamDependency &= ~0x80000000;
699 
700  // Ignore this for now ...
701  // Can be used for streams (re)prioritization - 5.3
702  Q_UNUSED(exclusive);
703  Q_UNUSED(weight);
704 }
705 
706 void QHttp2ProtocolHandler::handleRST_STREAM()
707 {
708  Q_ASSERT(inboundFrame.type() == FrameType::RST_STREAM);
709 
710  // "RST_STREAM frames MUST be associated with a stream.
711  // If a RST_STREAM frame is received with a stream identifier of 0x0,
712  // the recipient MUST treat this as a connection error (Section 5.4.1)
713  // of type PROTOCOL_ERROR.
714  const auto streamID = inboundFrame.streamID();
715  if (streamID == connectionStreamID)
716  return connectionError(PROTOCOL_ERROR, "RST_STREAM on 0x0");
717 
718  if (!(streamID & 0x1)) {
719  // RST_STREAM on a promised stream:
720  // since we do not keep track of such streams,
721  // just ignore.
722  return;
723  }
724 
725  if (streamID >= nextID) {
726  // "RST_STREAM frames MUST NOT be sent for a stream
727  // in the "idle" state. .. the recipient MUST treat this
728  // as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
729  return connectionError(PROTOCOL_ERROR, "RST_STREAM on idle stream");
730  }
731 
732  if (!activeStreams.contains(streamID)) {
733  // 'closed' stream, ignore.
734  return;
735  }
736 
737  Q_ASSERT(inboundFrame.dataSize() == 4);
738 
739  Stream &stream = activeStreams[streamID];
740  finishStreamWithError(stream, qFromBigEndian<quint32>(inboundFrame.dataBegin()));
741  markAsReset(stream.streamID);
742  deleteActiveStream(stream.streamID);
743 }
744 
745 void QHttp2ProtocolHandler::handleSETTINGS()
746 {
747  // 6.5 SETTINGS.
748  Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS);
749 
750  if (inboundFrame.streamID() != connectionStreamID)
751  return connectionError(PROTOCOL_ERROR, "SETTINGS on invalid stream");
752 
753  if (inboundFrame.flags().testFlag(FrameFlag::ACK)) {
754  if (!waitingForSettingsACK)
755  return connectionError(PROTOCOL_ERROR, "unexpected SETTINGS ACK");
756  waitingForSettingsACK = false;
757  return;
758  }
759 
760  if (inboundFrame.dataSize()) {
761  auto src = inboundFrame.dataBegin();
762  for (const uchar *end = src + inboundFrame.dataSize(); src != end; src += 6) {
763  const Settings identifier = Settings(qFromBigEndian<quint16>(src));
764  const quint32 intVal = qFromBigEndian<quint32>(src + 2);
765  if (!acceptSetting(identifier, intVal)) {
766  // If not accepted - we finish with connectionError.
767  return;
768  }
769  }
770  }
771 
772  sendSETTINGS_ACK();
773 }
774 
775 
776 void QHttp2ProtocolHandler::handlePUSH_PROMISE()
777 {
778  // 6.6 PUSH_PROMISE.
779  Q_ASSERT(inboundFrame.type() == FrameType::PUSH_PROMISE);
780 
781  if (!pushPromiseEnabled && prefaceSent && !waitingForSettingsACK) {
782  // This means, server ACKed our 'NO PUSH',
783  // but sent us PUSH_PROMISE anyway.
784  return connectionError(PROTOCOL_ERROR, "unexpected PUSH_PROMISE frame");
785  }
786 
787  const auto streamID = inboundFrame.streamID();
788  if (streamID == connectionStreamID) {
789  return connectionError(PROTOCOL_ERROR,
790  "PUSH_PROMISE with invalid associated stream (0x0)");
791  }
792 
793  if (!activeStreams.contains(streamID) && !streamWasReset(streamID)) {
794  return connectionError(ENHANCE_YOUR_CALM,
795  "PUSH_PROMISE with invalid associated stream");
796  }
797 
798  const auto reservedID = qFromBigEndian<quint32>(inboundFrame.dataBegin());
799  if ((reservedID & 1) || reservedID <= lastPromisedID ||
800  reservedID > Http2::lastValidStreamID) {
801  return connectionError(PROTOCOL_ERROR,
802  "PUSH_PROMISE with invalid promised stream ID");
803  }
804 
805  lastPromisedID = reservedID;
806 
807  if (!pushPromiseEnabled) {
808  // "ignoring a PUSH_PROMISE frame causes the stream state to become
809  // indeterminate" - let's send RST_STREAM frame with REFUSE_STREAM code.
810  resetPromisedStream(inboundFrame, Http2::REFUSE_STREAM);
811  }
812 
813  const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
814  continuedFrames.clear();
815  continuedFrames.push_back(std::move(inboundFrame));
816 
817  if (!endHeaders) {
818  continuationExpected = true;
819  return;
820  }
821 
822  handleContinuedHEADERS();
823 }
824 
825 void QHttp2ProtocolHandler::handlePING()
826 {
827  // Since we're implementing a client and not
828  // a server, we only reply to a PING, ACKing it.
829  Q_ASSERT(inboundFrame.type() == FrameType::PING);
831 
832  if (inboundFrame.streamID() != connectionStreamID)
833  return connectionError(PROTOCOL_ERROR, "PING on invalid stream");
834 
835  if (inboundFrame.flags() & FrameFlag::ACK)
836  return connectionError(PROTOCOL_ERROR, "unexpected PING ACK");
837 
838  Q_ASSERT(inboundFrame.dataSize() == 8);
839 
840  frameWriter.start(FrameType::PING, FrameFlag::ACK, connectionStreamID);
841  frameWriter.append(inboundFrame.dataBegin(), inboundFrame.dataBegin() + 8);
842  frameWriter.write(*m_socket);
843 }
844 
845 void QHttp2ProtocolHandler::handleGOAWAY()
846 {
847  // 6.8 GOAWAY
848 
849  Q_ASSERT(inboundFrame.type() == FrameType::GOAWAY);
850  // "An endpoint MUST treat a GOAWAY frame with a stream identifier
851  // other than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
852  if (inboundFrame.streamID() != connectionStreamID)
853  return connectionError(PROTOCOL_ERROR, "GOAWAY on invalid stream");
854 
855  const auto src = inboundFrame.dataBegin();
856  quint32 lastStreamID = qFromBigEndian<quint32>(src);
857  const quint32 errorCode = qFromBigEndian<quint32>(src + 4);
858 
859  if (!lastStreamID) {
860  // "The last stream identifier can be set to 0 if no
861  // streams were processed."
862  lastStreamID = 1;
863  } else if (!(lastStreamID & 0x1)) {
864  // 5.1.1 - we (client) use only odd numbers as stream identifiers.
865  return connectionError(PROTOCOL_ERROR, "GOAWAY with invalid last stream ID");
866  } else if (lastStreamID >= nextID) {
867  // "A server that is attempting to gracefully shut down a connection SHOULD
868  // send an initial GOAWAY frame with the last stream identifier set to 2^31-1
869  // and a NO_ERROR code."
870  if (lastStreamID != Http2::lastValidStreamID || errorCode != HTTP2_NO_ERROR)
871  return connectionError(PROTOCOL_ERROR, "GOAWAY invalid stream/error code");
872  } else {
873  lastStreamID += 2;
874  }
875 
876  goingAway = true;
877 
878  // For the requests (and streams) we did not start yet, we have to report an
879  // error.
881  "GOAWAY received, cannot start a request");
882  // Also, prevent further calls to sendRequest:
884 
887  qt_error(errorCode, error, message);
888 
889  // Even if the GOAWAY frame contains NO_ERROR we must send an error
890  // when terminating streams to ensure users can distinguish from a
891  // successful completion.
892  if (errorCode == HTTP2_NO_ERROR) {
894  message = QLatin1String("Server stopped accepting new streams before this stream was established");
895  }
896 
897  for (quint32 id = lastStreamID; id < nextID; id += 2) {
898  const auto it = activeStreams.find(id);
899  if (it != activeStreams.end()) {
900  Stream &stream = *it;
901  finishStreamWithError(stream, error, message);
902  markAsReset(id);
903  deleteActiveStream(id);
904  } else {
905  removeFromSuspended(id);
906  }
907  }
908 
909  if (!activeStreams.size())
910  closeSession();
911 }
912 
913 void QHttp2ProtocolHandler::handleWINDOW_UPDATE()
914 {
915  Q_ASSERT(inboundFrame.type() == FrameType::WINDOW_UPDATE);
916 
917 
918  const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
919  const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
920  const auto streamID = inboundFrame.streamID();
921 
922  if (streamID == Http2::connectionStreamID) {
923  if (!valid || sum_will_overflow(sessionSendWindowSize, delta))
924  return connectionError(PROTOCOL_ERROR, "WINDOW_UPDATE invalid delta");
925  sessionSendWindowSize += delta;
926  } else {
927  if (!activeStreams.contains(streamID)) {
928  // WINDOW_UPDATE on closed streams can be ignored.
929  return;
930  }
931  auto &stream = activeStreams[streamID];
932  if (!valid || sum_will_overflow(stream.sendWindow, delta)) {
933  finishStreamWithError(stream, QNetworkReply::ProtocolFailure,
934  QLatin1String("invalid WINDOW_UPDATE delta"));
935  sendRST_STREAM(streamID, PROTOCOL_ERROR);
936  markAsReset(streamID);
937  deleteActiveStream(streamID);
938  return;
939  }
940  stream.sendWindow += delta;
941  }
942 
943  // Since we're in _q_receiveReply at the moment, let's first handle other
944  // frames and resume suspended streams (if any) == start sending our own frame
945  // after handling these frames, since one them can be e.g. GOAWAY.
946  QMetaObject::invokeMethod(this, "resumeSuspendedStreams", Qt::QueuedConnection);
947 }
948 
949 void QHttp2ProtocolHandler::handleCONTINUATION()
950 {
951  Q_ASSERT(inboundFrame.type() == FrameType::CONTINUATION);
952  Q_ASSERT(continuedFrames.size()); // HEADERS frame must be already in.
953 
954  if (inboundFrame.streamID() != continuedFrames.front().streamID())
955  return connectionError(PROTOCOL_ERROR, "CONTINUATION on invalid stream");
956 
957  const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
958  continuedFrames.push_back(std::move(inboundFrame));
959 
960  if (!endHeaders)
961  return;
962 
963  continuationExpected = false;
964  handleContinuedHEADERS();
965 }
966 
967 void QHttp2ProtocolHandler::handleContinuedHEADERS()
968 {
969  // 'Continued' HEADERS can be: the initial HEADERS/PUSH_PROMISE frame
970  // with/without END_HEADERS flag set plus, if no END_HEADERS flag,
971  // a sequence of one or more CONTINUATION frames.
972  Q_ASSERT(continuedFrames.size());
973  const auto firstFrameType = continuedFrames[0].type();
974  Q_ASSERT(firstFrameType == FrameType::HEADERS ||
975  firstFrameType == FrameType::PUSH_PROMISE);
976 
977  const auto streamID = continuedFrames[0].streamID();
978 
979  if (firstFrameType == FrameType::HEADERS) {
980  if (activeStreams.contains(streamID)) {
981  Stream &stream = activeStreams[streamID];
982  if (stream.state != Stream::halfClosedLocal
983  && stream.state != Stream::remoteReserved
984  && stream.state != Stream::open) {
985  // We can receive HEADERS on streams initiated by our requests
986  // (these streams are in halfClosedLocal or open state) or
987  // remote-reserved streams from a server's PUSH_PROMISE.
988  finishStreamWithError(stream, QNetworkReply::ProtocolFailure,
989  QLatin1String("HEADERS on invalid stream"));
990  sendRST_STREAM(streamID, CANCEL);
991  markAsReset(streamID);
992  deleteActiveStream(streamID);
993  return;
994  }
995  } else if (!streamWasReset(streamID)) {
996  return connectionError(PROTOCOL_ERROR, "HEADERS on invalid stream");
997  }
998  // Else: we cannot just ignore our peer's HEADERS frames - they change
999  // HPACK context - even though the stream was reset; apparently the peer
1000  // has yet to see the reset.
1001  }
1002 
1003  std::vector<uchar> hpackBlock(assemble_hpack_block(continuedFrames));
1004  if (!hpackBlock.size()) {
1005  // It could be a PRIORITY sent in HEADERS - already handled by this
1006  // point in handleHEADERS. If it was PUSH_PROMISE (HTTP/2 8.2.1):
1007  // "The header fields in PUSH_PROMISE and any subsequent CONTINUATION
1008  // frames MUST be a valid and complete set of request header fields
1009  // (Section 8.1.2.3) ... If a client receives a PUSH_PROMISE that does
1010  // not include a complete and valid set of header fields or the :method
1011  // pseudo-header field identifies a method that is not safe, it MUST
1012  // respond with a stream error (Section 5.4.2) of type PROTOCOL_ERROR."
1013  if (firstFrameType == FrameType::PUSH_PROMISE)
1014  resetPromisedStream(continuedFrames[0], Http2::PROTOCOL_ERROR);
1015 
1016  return;
1017  }
1018 
1019  HPack::BitIStream inputStream{&hpackBlock[0], &hpackBlock[0] + hpackBlock.size()};
1020  if (!decoder.decodeHeaderFields(inputStream))
1021  return connectionError(COMPRESSION_ERROR, "HPACK decompression failed");
1022 
1023  switch (firstFrameType) {
1024  case FrameType::HEADERS:
1025  if (activeStreams.contains(streamID)) {
1026  Stream &stream = activeStreams[streamID];
1027  updateStream(stream, decoder.decodedHeader());
1028  // Needs to resend the request; we should finish and delete the current stream
1029  const bool needResend = stream.request().d->needResendWithCredentials;
1030  // No DATA frames. Or needs to resend.
1031  if (continuedFrames[0].flags() & FrameFlag::END_STREAM || needResend) {
1032  finishStream(stream);
1033  deleteActiveStream(stream.streamID);
1034  }
1035  }
1036  break;
1037  case FrameType::PUSH_PROMISE:
1038  if (!tryReserveStream(continuedFrames[0], decoder.decodedHeader()))
1039  resetPromisedStream(continuedFrames[0], Http2::PROTOCOL_ERROR);
1040  break;
1041  default:
1042  break;
1043  }
1044 }
1045 
1046 bool QHttp2ProtocolHandler::acceptSetting(Http2::Settings identifier, quint32 newValue)
1047 {
1048  if (identifier == Settings::HEADER_TABLE_SIZE_ID) {
1049  if (newValue > maxAcceptableTableSize) {
1050  connectionError(PROTOCOL_ERROR, "SETTINGS invalid table size");
1051  return false;
1052  }
1053  encoder.setMaxDynamicTableSize(newValue);
1054  }
1055 
1056  if (identifier == Settings::INITIAL_WINDOW_SIZE_ID) {
1057  // For every active stream - adjust its window
1058  // (and handle possible overflows as errors).
1059  if (newValue > quint32(std::numeric_limits<qint32>::max())) {
1060  connectionError(FLOW_CONTROL_ERROR, "SETTINGS invalid initial window size");
1061  return false;
1062  }
1063 
1064  const qint32 delta = qint32(newValue) - streamInitialSendWindowSize;
1065  streamInitialSendWindowSize = newValue;
1066 
1067  std::vector<quint32> brokenStreams;
1068  brokenStreams.reserve(activeStreams.size());
1069  for (auto &stream : activeStreams) {
1070  if (sum_will_overflow(stream.sendWindow, delta)) {
1071  brokenStreams.push_back(stream.streamID);
1072  continue;
1073  }
1074  stream.sendWindow += delta;
1075  }
1076 
1077  for (auto id : brokenStreams) {
1078  auto &stream = activeStreams[id];
1079  finishStreamWithError(stream, QNetworkReply::ProtocolFailure,
1080  QLatin1String("SETTINGS window overflow"));
1081  sendRST_STREAM(id, PROTOCOL_ERROR);
1082  markAsReset(id);
1083  deleteActiveStream(id);
1084  }
1085 
1086  QMetaObject::invokeMethod(this, "resumeSuspendedStreams", Qt::QueuedConnection);
1087  }
1088 
1089  if (identifier == Settings::MAX_CONCURRENT_STREAMS_ID)
1090  maxConcurrentStreams = newValue;
1091 
1092  if (identifier == Settings::MAX_FRAME_SIZE_ID) {
1093  if (newValue < Http2::minPayloadLimit || newValue > Http2::maxPayloadSize) {
1094  connectionError(PROTOCOL_ERROR, "SETTINGS max frame size is out of range");
1095  return false;
1096  }
1097  maxFrameSize = newValue;
1098  }
1099 
1100  if (identifier == Settings::MAX_HEADER_LIST_SIZE_ID) {
1101  // We just remember this value, it can later
1102  // prevent us from sending any request (and this
1103  // will end up in request/reply error).
1104  maxHeaderListSize = newValue;
1105  }
1106 
1107  return true;
1108 }
1109 
1110 void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader &headers,
1111  Qt::ConnectionType connectionType)
1112 {
1113  const auto httpReply = stream.reply();
1114  auto &httpRequest = stream.request();
1115  Q_ASSERT(httpReply || stream.state == Stream::remoteReserved);
1116 
1117  if (!httpReply) {
1118  // It's a PUSH_PROMISEd HEADERS, no actual request/reply
1119  // exists yet, we have to cache this data for a future
1120  // (potential) request.
1121 
1122  // TODO: the part with assignment is not especially cool
1123  // or beautiful, good that at least QByteArray is implicitly
1124  // sharing data. To be refactored (std::move).
1125  Q_ASSERT(promisedData.contains(stream.key));
1126  PushPromise &promise = promisedData[stream.key];
1127  promise.responseHeader = headers;
1128  return;
1129  }
1130 
1131  const auto httpReplyPrivate = httpReply->d_func();
1132 
1133  // For HTTP/1 'location' is handled (and redirect URL set) when a protocol
1134  // handler emits channel->allDone(). Http/2 protocol handler never emits
1135  // allDone, since we have many requests multiplexed in one channel at any
1136  // moment and we are probably not done yet. So we extract url and set it
1137  // here, if needed.
1138  int statusCode = 0;
1139  for (const auto &pair : headers) {
1140  const auto &name = pair.name;
1141  auto value = pair.value;
1142 
1143  // TODO: part of this code copies what SPDY protocol handler does when
1144  // processing headers. Binary nature of HTTP/2 and SPDY saves us a lot
1145  // of parsing and related errors/bugs, but it would be nice to have
1146  // more detailed validation of headers.
1147  if (name == ":status") {
1148  statusCode = value.left(3).toInt();
1149  httpReply->setStatusCode(statusCode);
1150  m_channel->lastStatus = statusCode; // Mostly useless for http/2, needed for auth
1151  httpReply->setReasonPhrase(QString::fromLatin1(value.mid(4)));
1152  } else if (name == ":version") {
1153  httpReply->setMajorVersion(value.at(5) - '0');
1154  httpReply->setMinorVersion(value.at(7) - '0');
1155  } else if (name == "content-length") {
1156  bool ok = false;
1157  const qlonglong length = value.toLongLong(&ok);
1158  if (ok)
1159  httpReply->setContentLength(length);
1160  } else {
1161  QByteArray binder(", ");
1162  if (name == "set-cookie")
1163  binder = "\n";
1164  httpReply->appendHeaderField(name, value.replace('\0', binder));
1165  }
1166  }
1167 
1168  const auto handleAuth = [&, this](const QByteArray &authField, bool isProxy) -> bool {
1169  Q_ASSERT(httpReply);
1170  const auto auth = authField.trimmed();
1171  if (auth.startsWith("Negotiate") || auth.startsWith("NTLM")) {
1172  // @todo: We're supposed to fall back to http/1.1:
1173  // https://docs.microsoft.com/en-us/iis/get-started/whats-new-in-iis-10/http2-on-iis#when-is-http2-not-supported
1174  // "Windows authentication (NTLM/Kerberos/Negotiate) is not supported with HTTP/2.
1175  // In this case IIS will fall back to HTTP/1.1."
1176  // Though it might be OK to ignore this. The server shouldn't let us connect with
1177  // HTTP/2 if it doesn't support us using it.
1178  } else if (!auth.isEmpty()) {
1179  // Somewhat mimics parts of QHttpNetworkConnectionChannel::handleStatus
1180  bool resend = false;
1181  const bool authenticateHandled = m_connection->d_func()->handleAuthenticateChallenge(
1182  m_socket, httpReply, isProxy, resend);
1183  if (authenticateHandled && resend) {
1184  httpReply->d_func()->eraseData();
1185  // Add the request back in queue, we'll retry later now that
1186  // we've gotten some username/password set on it:
1187  httpRequest.d->needResendWithCredentials = true;
1188  m_channel->h2RequestsToSend.insert(httpRequest.priority(), stream.httpPair);
1189  httpReply->d_func()->clearHeaders();
1190  // If we have data we were uploading we need to reset it:
1191  if (stream.data()) {
1192  stream.data()->reset();
1193  httpReplyPrivate->totallyUploadedData = 0;
1194  }
1195  return true;
1196  } // else: Authentication failed or was cancelled
1197  }
1198  return false;
1199  };
1200 
1201  if (httpReply) {
1202  // See Note further down. These statuses would in HTTP/1.1 be handled
1203  // by QHttpNetworkConnectionChannel::handleStatus. But because h2 has
1204  // multiple streams/requests in a single channel this structure does not
1205  // map properly to that function.
1206  if (httpReply->statusCode() == 401) {
1207  const auto wwwAuth = httpReply->headerField("www-authenticate");
1208  if (handleAuth(wwwAuth, false)) {
1209  sendRST_STREAM(stream.streamID, CANCEL);
1210  markAsReset(stream.streamID);
1211  // The stream is finalized and deleted after returning
1212  return;
1213  } // else: errors handled later
1214  } else if (httpReply->statusCode() == 407) {
1215  const auto proxyAuth = httpReply->headerField("proxy-authenticate");
1216  if (handleAuth(proxyAuth, true)) {
1217  sendRST_STREAM(stream.streamID, CANCEL);
1218  markAsReset(stream.streamID);
1219  // The stream is finalized and deleted after returning
1220  return;
1221  } // else: errors handled later
1222  }
1223  }
1224 
1225  if (QHttpNetworkReply::isHttpRedirect(statusCode) && httpRequest.isFollowRedirects()) {
1227  m_connection->d_func()->parseRedirectResponse(httpReply);
1228  if (result.errorCode != QNetworkReply::NoError) {
1229  auto errorString = m_connection->d_func()->errorDetail(result.errorCode, m_socket);
1230  finishStreamWithError(stream, result.errorCode, errorString);
1231  sendRST_STREAM(stream.streamID, INTERNAL_ERROR);
1232  markAsReset(stream.streamID);
1233  return;
1234  }
1235 
1236  if (result.redirectUrl.isValid())
1237  httpReply->setRedirectUrl(result.redirectUrl);
1238  }
1239 
1240  if (httpReplyPrivate->isCompressed() && httpRequest.d->autoDecompress)
1241  httpReplyPrivate->removeAutoDecompressHeader();
1242 
1243  if (QHttpNetworkReply::isHttpRedirect(statusCode)) {
1244  // Note: This status code can trigger uploadByteDevice->reset() in
1245  // QHttpNetworkConnectionChannel::handleStatus. Alas, we have no single
1246  // request/reply, we multiplex several requests and thus we never simply
1247  // call 'handleStatus'. If we have a byte-device - we try to reset it
1248  // here, we don't (and can't) handle any error during reset operation.
1249  if (stream.data()) {
1250  stream.data()->reset();
1251  httpReplyPrivate->totallyUploadedData = 0;
1252  }
1253  }
1254 
1255  if (connectionType == Qt::DirectConnection)
1256  emit httpReply->headerChanged();
1257  else
1258  QMetaObject::invokeMethod(httpReply, "headerChanged", connectionType);
1259 }
1260 
1261 void QHttp2ProtocolHandler::updateStream(Stream &stream, const Frame &frame,
1262  Qt::ConnectionType connectionType)
1263 {
1264  Q_ASSERT(frame.type() == FrameType::DATA);
1265  auto httpReply = stream.reply();
1266  Q_ASSERT(httpReply || stream.state == Stream::remoteReserved);
1267 
1268  if (!httpReply) {
1269  Q_ASSERT(promisedData.contains(stream.key));
1270  PushPromise &promise = promisedData[stream.key];
1271  // TODO: refactor this to use std::move.
1272  promise.dataFrames.push_back(frame);
1273  return;
1274  }
1275 
1276  if (const auto length = frame.dataSize()) {
1277  const char *data = reinterpret_cast<const char *>(frame.dataBegin());
1278  auto replyPrivate = httpReply->d_func();
1279 
1280  replyPrivate->totalProgress += length;
1281 
1282  const QByteArray wrapped(data, length);
1283  replyPrivate->responseData.append(wrapped);
1284 
1285  if (replyPrivate->shouldEmitSignals()) {
1286  if (connectionType == Qt::DirectConnection) {
1287  emit httpReply->readyRead();
1288  emit httpReply->dataReadProgress(replyPrivate->totalProgress,
1289  replyPrivate->bodyLength);
1290  } else {
1291  QMetaObject::invokeMethod(httpReply, "readyRead", connectionType);
1292  QMetaObject::invokeMethod(httpReply, "dataReadProgress", connectionType,
1293  Q_ARG(qint64, replyPrivate->totalProgress),
1294  Q_ARG(qint64, replyPrivate->bodyLength));
1295  }
1296  }
1297  }
1298 }
1299 
1300 void QHttp2ProtocolHandler::finishStream(Stream &stream, Qt::ConnectionType connectionType)
1301 {
1302  Q_ASSERT(stream.state == Stream::remoteReserved || stream.reply());
1303 
1304  stream.state = Stream::closed;
1305  auto httpReply = stream.reply();
1306  if (httpReply) {
1307  httpReply->disconnect(this);
1308  if (stream.data())
1309  stream.data()->disconnect(this);
1310 
1311  if (!stream.request().d->needResendWithCredentials) {
1312  if (connectionType == Qt::DirectConnection)
1313  emit httpReply->finished();
1314  else
1315  QMetaObject::invokeMethod(httpReply, "finished", connectionType);
1316  }
1317  }
1318 
1319  qCDebug(QT_HTTP2) << "stream" << stream.streamID << "closed";
1320 }
1321 
1322 void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, quint32 errorCode)
1323 {
1325  QString message;
1326  qt_error(errorCode, error, message);
1327  finishStreamWithError(stream, error, message);
1328 }
1329 
1330 void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, QNetworkReply::NetworkError error,
1331  const QString &message)
1332 {
1333  Q_ASSERT(stream.state == Stream::remoteReserved || stream.reply());
1334 
1335  stream.state = Stream::closed;
1336  if (auto httpReply = stream.reply()) {
1337  httpReply->disconnect(this);
1338  if (stream.data())
1339  stream.data()->disconnect(this);
1340 
1341  // TODO: error message must be translated!!! (tr)
1342  emit httpReply->finishedWithError(error, message);
1343  }
1344 
1345  qCWarning(QT_HTTP2) << "stream" << stream.streamID
1346  << "finished with error:" << message;
1347 }
1348 
1349 quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message, bool uploadDone)
1350 {
1351  const qint32 newStreamID = allocateStreamID();
1352  if (!newStreamID)
1353  return 0;
1354 
1355  Q_ASSERT(!activeStreams.contains(newStreamID));
1356 
1357  const auto reply = message.second;
1358  const auto replyPrivate = reply->d_func();
1359  replyPrivate->connection = m_connection;
1360  replyPrivate->connectionChannel = m_channel;
1361  reply->setHttp2WasUsed(true);
1362  streamIDs.insert(reply, newStreamID);
1364  this, SLOT(_q_replyDestroyed(QObject*)));
1365 
1366  const Stream newStream(message, newStreamID,
1367  streamInitialSendWindowSize,
1368  streamInitialReceiveWindowSize);
1369 
1370  if (!uploadDone) {
1371  if (auto src = newStream.data()) {
1372  connect(src, SIGNAL(readyRead()), this,
1373  SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection);
1375  this, &QHttp2ProtocolHandler::_q_uploadDataDestroyed);
1376  streamIDs.insert(src, newStreamID);
1377  }
1378  }
1379 
1381 
1382  activeStreams.insert(newStreamID, newStream);
1383 
1384  return newStreamID;
1385 }
1386 
1387 void QHttp2ProtocolHandler::addToSuspended(Stream &stream)
1388 {
1389  qCDebug(QT_HTTP2) << "stream" << stream.streamID
1390  << "suspended by flow control";
1391  const auto priority = stream.priority();
1392  Q_ASSERT(int(priority) >= 0 && int(priority) < 3);
1393  suspendedStreams[priority].push_back(stream.streamID);
1394 }
1395 
1396 void QHttp2ProtocolHandler::markAsReset(quint32 streamID)
1397 {
1398  Q_ASSERT(streamID);
1399 
1400  qCDebug(QT_HTTP2) << "stream" << streamID << "was reset";
1401  // This part is quite tricky: I have to clear this set
1402  // so that it does not become tOOO big.
1403  if (recycledStreams.size() > maxRecycledStreams) {
1404  // At least, I'm erasing the oldest first ...
1405  recycledStreams.erase(recycledStreams.begin(),
1406  recycledStreams.begin() +
1407  recycledStreams.size() / 2);
1408  }
1409 
1410  const auto it = std::lower_bound(recycledStreams.begin(), recycledStreams.end(),
1411  streamID);
1412  if (it != recycledStreams.end() && *it == streamID)
1413  return;
1414 
1415  recycledStreams.insert(it, streamID);
1416 }
1417 
1418 quint32 QHttp2ProtocolHandler::popStreamToResume()
1419 {
1420  quint32 streamID = connectionStreamID;
1421  using QNR = QHttpNetworkRequest;
1422  const QNR::Priority ranks[] = {QNR::HighPriority,
1423  QNR::NormalPriority,
1424  QNR::LowPriority};
1425 
1426  for (const QNR::Priority rank : ranks) {
1427  auto &queue = suspendedStreams[rank];
1428  auto it = queue.begin();
1429  for (; it != queue.end(); ++it) {
1430  if (!activeStreams.contains(*it))
1431  continue;
1432  if (activeStreams[*it].sendWindow > 0)
1433  break;
1434  }
1435 
1436  if (it != queue.end()) {
1437  streamID = *it;
1438  queue.erase(it);
1439  break;
1440  }
1441  }
1442 
1443  return streamID;
1444 }
1445 
1446 void QHttp2ProtocolHandler::removeFromSuspended(quint32 streamID)
1447 {
1448  for (auto &q : suspendedStreams) {
1449  q.erase(std::remove(q.begin(), q.end(), streamID), q.end());
1450  }
1451 }
1452 
1453 void QHttp2ProtocolHandler::deleteActiveStream(quint32 streamID)
1454 {
1455  if (activeStreams.contains(streamID)) {
1456  auto &stream = activeStreams[streamID];
1457  if (stream.reply()) {
1458  stream.reply()->disconnect(this);
1459  streamIDs.remove(stream.reply());
1460  }
1461  if (stream.data()) {
1462  stream.data()->disconnect(this);
1463  streamIDs.remove(stream.data());
1464  }
1465  activeStreams.remove(streamID);
1466  }
1467 
1468  removeFromSuspended(streamID);
1470  QMetaObject::invokeMethod(this, "sendRequest", Qt::QueuedConnection);
1471 }
1472 
1473 bool QHttp2ProtocolHandler::streamWasReset(quint32 streamID) const
1474 {
1475  const auto it = std::lower_bound(recycledStreams.begin(),
1476  recycledStreams.end(),
1477  streamID);
1478  return it != recycledStreams.end() && *it == streamID;
1479 }
1480 
1481 void QHttp2ProtocolHandler::resumeSuspendedStreams()
1482 {
1483  while (sessionSendWindowSize > 0) {
1484  const auto streamID = popStreamToResume();
1485  if (!streamID)
1486  return;
1487 
1488  if (!activeStreams.contains(streamID))
1489  continue;
1490 
1491  Stream &stream = activeStreams[streamID];
1492  if (!sendDATA(stream)) {
1493  finishStreamWithError(stream, QNetworkReply::UnknownNetworkError,
1494  QLatin1String("failed to send DATA"));
1495  sendRST_STREAM(streamID, INTERNAL_ERROR);
1496  markAsReset(streamID);
1497  deleteActiveStream(streamID);
1498  }
1499  }
1500 }
1501 
1502 quint32 QHttp2ProtocolHandler::allocateStreamID()
1503 {
1504  // With protocol upgrade streamID == 1 will become
1505  // invalid. The logic must be updated.
1506  if (nextID > Http2::lastValidStreamID)
1507  return 0;
1508 
1509  const quint32 streamID = nextID;
1510  nextID += 2;
1511 
1512  return streamID;
1513 }
1514 
1515 bool QHttp2ProtocolHandler::tryReserveStream(const Http2::Frame &pushPromiseFrame,
1516  const HPack::HttpHeader &requestHeader)
1517 {
1518  Q_ASSERT(pushPromiseFrame.type() == FrameType::PUSH_PROMISE);
1519 
1520  QMap<QByteArray, QByteArray> pseudoHeaders;
1521  for (const auto &field : requestHeader) {
1522  if (field.name == ":scheme" || field.name == ":path"
1523  || field.name == ":authority" || field.name == ":method") {
1524  if (field.value.isEmpty() || pseudoHeaders.contains(field.name))
1525  return false;
1526  pseudoHeaders[field.name] = field.value;
1527  }
1528  }
1529 
1530  if (pseudoHeaders.size() != 4) {
1531  // All four required, HTTP/2 8.1.2.3.
1532  return false;
1533  }
1534 
1535  const QByteArray method = pseudoHeaders[":method"];
1536  if (method.compare("get", Qt::CaseInsensitive) != 0 &&
1537  method.compare("head", Qt::CaseInsensitive) != 0)
1538  return false;
1539 
1540  QUrl url;
1541  url.setScheme(QLatin1String(pseudoHeaders[":scheme"]));
1542  url.setAuthority(QLatin1String(pseudoHeaders[":authority"]));
1543  url.setPath(QLatin1String(pseudoHeaders[":path"]));
1544 
1545  if (!url.isValid())
1546  return false;
1547 
1548  Q_ASSERT(activeStreams.contains(pushPromiseFrame.streamID()));
1549  const Stream &associatedStream = activeStreams[pushPromiseFrame.streamID()];
1550 
1551  const auto associatedUrl = urlkey_from_request(associatedStream.request());
1552  if (url.adjusted(QUrl::RemovePath) != associatedUrl.adjusted(QUrl::RemovePath))
1553  return false;
1554 
1555  const auto urlKey = url.toString();
1556  if (promisedData.contains(urlKey)) // duplicate push promise
1557  return false;
1558 
1559  const auto reservedID = qFromBigEndian<quint32>(pushPromiseFrame.dataBegin());
1560  // By this time all sanity checks on reservedID were done already
1561  // in handlePUSH_PROMISE. We do not repeat them, only those below:
1562  Q_ASSERT(!activeStreams.contains(reservedID));
1563  Q_ASSERT(!streamWasReset(reservedID));
1564 
1565  auto &promise = promisedData[urlKey];
1566  promise.reservedID = reservedID;
1567  promise.pushHeader = requestHeader;
1568 
1569  activeStreams.insert(reservedID, Stream(urlKey, reservedID, streamInitialReceiveWindowSize));
1570  return true;
1571 }
1572 
1573 void QHttp2ProtocolHandler::resetPromisedStream(const Frame &pushPromiseFrame,
1574  Http2::Http2Error reason)
1575 {
1576  Q_ASSERT(pushPromiseFrame.type() == FrameType::PUSH_PROMISE);
1577  const auto reservedID = qFromBigEndian<quint32>(pushPromiseFrame.dataBegin());
1578  sendRST_STREAM(reservedID, reason);
1579  markAsReset(reservedID);
1580 }
1581 
1582 void QHttp2ProtocolHandler::initReplyFromPushPromise(const HttpMessagePair &message,
1583  const QString &cacheKey)
1584 {
1585  Q_ASSERT(promisedData.contains(cacheKey));
1586  auto promise = promisedData.take(cacheKey);
1587  Q_ASSERT(message.second);
1588  message.second->setHttp2WasUsed(true);
1589 
1590  qCDebug(QT_HTTP2) << "found cached/promised response on stream" << promise.reservedID;
1591 
1592  bool replyFinished = false;
1593  Stream *promisedStream = nullptr;
1594  if (activeStreams.contains(promise.reservedID)) {
1595  promisedStream = &activeStreams[promise.reservedID];
1596  // Ok, we have an active (not closed yet) stream waiting for more frames,
1597  // let's pretend we requested it:
1598  promisedStream->httpPair = message;
1599  } else {
1600  // Let's pretent we're sending a request now:
1601  Stream closedStream(message, promise.reservedID,
1602  streamInitialSendWindowSize,
1603  streamInitialReceiveWindowSize);
1604  closedStream.state = Stream::halfClosedLocal;
1605  activeStreams.insert(promise.reservedID, closedStream);
1606  promisedStream = &activeStreams[promise.reservedID];
1607  replyFinished = true;
1608  }
1609 
1610  Q_ASSERT(promisedStream);
1611 
1612  if (!promise.responseHeader.empty())
1613  updateStream(*promisedStream, promise.responseHeader, Qt::QueuedConnection);
1614 
1615  for (const auto &frame : promise.dataFrames)
1616  updateStream(*promisedStream, frame, Qt::QueuedConnection);
1617 
1618  if (replyFinished) {
1619  // Good, we already have received ALL the frames of that PUSH_PROMISE,
1620  // nothing more to do.
1621  finishStream(*promisedStream, Qt::QueuedConnection);
1622  deleteActiveStream(promisedStream->streamID);
1623  }
1624 }
1625 
1626 void QHttp2ProtocolHandler::connectionError(Http2::Http2Error errorCode,
1627  const char *message)
1628 {
1629  Q_ASSERT(message);
1630  Q_ASSERT(!goingAway);
1631 
1632  qCCritical(QT_HTTP2) << "connection error:" << message;
1633 
1634  goingAway = true;
1635  sendGOAWAY(errorCode);
1636  const auto error = qt_error(errorCode);
1638 
1639  for (auto &stream: activeStreams)
1640  finishStreamWithError(stream, error, QLatin1String(message));
1641 
1642  closeSession();
1643 }
1644 
1645 void QHttp2ProtocolHandler::closeSession()
1646 {
1647  activeStreams.clear();
1648  for (auto &q: suspendedStreams)
1649  q.clear();
1650  recycledStreams.clear();
1651 
1652  m_channel->close();
1653 }
1654 
small capitals from c petite p scientific i
[1]
Definition: afcover.h:80
FT_Error error
Definition: cffdrivr.c:657
bool decodeHeaderFields(class BitIStream &inputStream)
Definition: hpack.cpp:414
const HttpHeader & decodedHeader() const
Definition: hpack_p.h:125
void setCompressStrings(bool compress)
Definition: hpack.cpp:211
void setMaxDynamicTableSize(quint32 size)
Definition: hpack.cpp:204
Frame & inboundFrame()
FrameStatus read(QAbstractSocket &socket)
void setPayloadSize(quint32 size)
void append(ValueType val)
void setOutboundFrame(Frame &&newFrame)
bool writeDATA(QAbstractSocket &socket, quint32 sizeLimit, const uchar *src, quint32 size)
Frame & outboundFrame()
void start(FrameType type, FrameFlags flags, quint32 streamID)
bool write(QAbstractSocket &socket) const
QHttpNetworkConnectionChannel * m_channel
QHttpNetworkConnection * m_connection
The QByteArray class provides an array of bytes.
Definition: qbytearray.h:85
QByteArray trimmed() const &
Definition: qbytearray.h:220
static QString translate(const char *context, const char *key, const char *disambiguation=nullptr, int n=-1)
bool remove(const Key &key)
Definition: qhash.h:911
T take(const Key &key)
Definition: qhash.h:929
bool contains(const Key &key) const noexcept
Definition: qhash.h:944
T value(const Key &key) const noexcept
Definition: qhash.h:997
iterator insert(const Key &key, const T &value)
Definition: qhash.h:1228
QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *channel)
Q_INVOKABLE void handleConnectionClosure()
Q_INVOKABLE void ensureClientPrefaceSent()
void emitFinishedWithError(QNetworkReply::NetworkError error, const char *message)
QMultiMap< int, HttpMessagePair > h2RequestsToSend
QHttp2Configuration http2Parameters() const
static bool isHttpRedirect(int statusCode)
qint64 write(const char *data, qint64 len)
Definition: qiodevice.cpp:1681
The QLatin1String class provides a thin wrapper around an US-ASCII/Latin-1 encoded string literal.
Definition: qstring.h:84
T value(const Key &key, const T &defaultValue=T()) const
Definition: qmap.h:392
bool contains(const Key &key) const
Definition: qmap.h:376
size_type size() const
Definition: qmap.h:302
iterator insert(const Key &key, const T &value)
Definition: qmap.h:1453
size_type size() const
Definition: qmap.h:943
void clear()
Definition: qmap.h:965
QVariant header(KnownHeaders header) const
Q_INVOKABLE QObject(QObject *parent=nullptr)
Definition: qobject.cpp:913
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
Definition: qobject.cpp:2772
QObject * sender() const
Definition: qobject.cpp:2472
void destroyed(QObject *=nullptr)
The QString class provides a Unicode character string.
Definition: qstring.h:388
static QString fromLatin1(QByteArrayView ba)
Definition: qstring.cpp:5488
QUrl(const QString &url, ParsingMode parsingMode)
Definition: qurl.cpp:1858
@ RemovePath
Definition: qurl.h:146
@ RemoveUserInfo
Definition: qurl.h:143
@ FullyEncoded
Definition: qurl.h:165
void resize(int w, int h)
Definition: qwidget.h:916
QQueue< int > queue
[0]
#define true
Definition: ftrandom.c:51
std::vector< HeaderField > HttpHeader
Definition: hpack_p.h:67
HeaderSize header_size(const HttpHeader &header)
Definition: hpack.cpp:53
HeaderSize entry_size(const QByteArray &name, const QByteArray &value)
Definition: hpacktable.cpp:55
QPair< bool, quint32 > HeaderSize
Definition: hpacktable_p.h:89
Frame configurationToSettingsFrame(const QHttp2Configuration &config)
const char Http2clientPreface[clientPrefaceLength]
const quint32 lastValidStreamID((quint32(1)<< 31) - 1)
@ frameHeaderSize
@ defaultSessionWindowSize
@ maxPayloadSize
@ connectionStreamID
@ clientPrefaceLength
@ COMPRESSION_ERROR
@ PROTOCOL_ERROR
@ ENHANCE_YOUR_CALM
@ HTTP2_NO_ERROR
@ FLOW_CONTROL_ERROR
@ FRAME_SIZE_ERROR
@ INTERNAL_ERROR
void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, QString &errorMessage)
HPack::HttpHeader build_headers(const QHttpNetworkRequest &request, quint32 maxHeaderListSize, bool useProxy)
bool sum_will_overflow(qint32 windowSize, qint32 delta)
std::vector< uchar > assemble_hpack_block(const std::vector< Http2::Frame > &frames)
QUrl urlkey_from_request(const QHttpNetworkRequest &request)
@ CaseInsensitive
Definition: qnamespace.h:1283
ConnectionType
Definition: qnamespace.h:1304
@ QueuedConnection
Definition: qnamespace.h:1307
@ DirectConnection
Definition: qnamespace.h:1306
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char * method
EGLStreamKHR stream
EGLOutputLayerEXT EGLint EGLAttrib value
unsigned int quint32
Definition: qglobal.h:288
QT_BEGIN_INCLUDE_NAMESPACE typedef unsigned char uchar
Definition: qglobal.h:332
int qint32
Definition: qglobal.h:287
long long qint64
Definition: qglobal.h:298
qint64 qlonglong
Definition: qglobal.h:301
QPair< QHttpNetworkRequest, QHttpNetworkReply * > HttpMessagePair
#define qCCritical(category,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define SLOT(a)
Definition: qobjectdefs.h:87
#define Q_ARG(type, data)
Definition: qobjectdefs.h:98
#define SIGNAL(a)
Definition: qobjectdefs.h:88
GLenum GLuint GLenum GLsizei length
Definition: qopengl.h:270
GLenum GLuint id
[6]
Definition: qopengl.h:270
GLenum GLuint GLenum GLsizei const GLchar * message
Definition: qopengl.h:270
GLenum GLsizei GLuint GLint * bytesWritten
GLuint64 key
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLuint end
GLuint GLuint GLfloat weight
GLenum src
GLenum GLenum dst
GLbitfield flags
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint name
GLdouble GLdouble GLdouble GLdouble q
Definition: qopenglext.h:259
GLuint64EXT * result
[6]
Definition: qopenglext.h:10932
#define Q_ASSERT(cond)
Definition: qrandom.cpp:84
#define emit
Definition: qtmetamacros.h:85
Q_UNUSED(salary)
[21]
settings remove("monkey")
QUrl url("http://www.example.com/List of holidays.xml")
[0]
QFrame frame
[0]
false readyRead(responseheader) dataReadProgress(18300
QHttpRequestHeader header("GET", QUrl::toPercentEncoding("/index.html"))
[1]
QNetworkRequest request(url)
QNetworkReply * reply
void replyFinished(QNetworkReply *reply)
[1]
QStringList::Iterator it
const uchar * dataBegin() const
bool priority(quint32 *streamID=nullptr, uchar *weight=nullptr) const
quint32 dataSize() const
FrameType type() const
Definition: http2frames.cpp:59
quint32 payloadSize() const
Definition: http2frames.cpp:81
FrameFlags flags() const
Definition: http2frames.cpp:75
quint32 streamID() const
Definition: http2frames.cpp:69
std::vector< Frame > dataFrames
HPack::HttpHeader responseHeader
HPack::HttpHeader pushHeader
QNonContiguousByteDevice * data() const
HttpMessagePair httpPair
const QHttpNetworkRequest & request() const
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
@ windowSize