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 test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30
31#include <QtNetwork/private/http2protocol_p.h>
32#include <QtNetwork/private/bitstreams_p.h>
33
34#include "http2srv.h"
35
36#ifndef QT_NO_SSL
37#include <QtNetwork/qsslconfiguration.h>
38#include <QtNetwork/qsslsocket.h>
39#include <QtNetwork/qsslkey.h>
40#endif
41
42#include <QtNetwork/qtcpsocket.h>
43
44#include <QtCore/qtimer.h>
45#include <QtCore/qdebug.h>
46#include <QtCore/qlist.h>
47#include <QtCore/qfile.h>
48
49#include <cstdlib>
50#include <cstring>
51#include <limits>
52
53QT_BEGIN_NAMESPACE
54
55using namespace Http2;
56using namespace HPack;
57
58namespace
59{
60
61inline bool is_valid_client_stream(quint32 streamID)
62{
63 // A valid client stream ID is an odd integer number in the range [1, INT_MAX].
64 return (streamID & 0x1) && streamID <= quint32(std::numeric_limits<qint32>::max());
65}
66
67void fill_push_header(const HttpHeader &originalRequest, HttpHeader &promisedRequest)
68{
69 for (const auto &field : originalRequest) {
70 if (field.name == QByteArray(":authority") ||
71 field.name == QByteArray(":scheme")) {
72 promisedRequest.push_back(x: field);
73 }
74 }
75}
76
77}
78
79Http2Server::Http2Server(H2Type type, const RawSettings &ss, const RawSettings &cs)
80 : connectionType(type),
81 serverSettings(ss),
82 expectedClientSettings(cs)
83{
84#if !QT_CONFIG(ssl)
85 Q_ASSERT(type != H2Type::h2Alpn && type != H2Type::h2Direct);
86#endif
87
88 responseBody = "<html>\n"
89 "<head>\n"
90 "<title>Sample \"Hello, World\" Application</title>\n"
91 "</head>\n"
92 "<body bgcolor=white>\n"
93 "<table border=\"0\" cellpadding=\"10\">\n"
94 "<tr>\n"
95 "<td>\n"
96 "<img src=\"images/springsource.png\">\n"
97 "</td>\n"
98 "<td>\n"
99 "<h1>Sample \"Hello, World\" Application</h1>\n"
100 "</td>\n"
101 "</tr>\n"
102 "</table>\n"
103 "<p>This is the home page for the HelloWorld Web application. </p>\n"
104 "</body>\n"
105 "</html>";
106}
107
108Http2Server::~Http2Server()
109{
110}
111
112void Http2Server::enablePushPromise(bool pushEnabled, const QByteArray &path)
113{
114 pushPromiseEnabled = pushEnabled;
115 pushPath = path;
116}
117
118void Http2Server::setResponseBody(const QByteArray &body)
119{
120 responseBody = body;
121}
122
123void Http2Server::setAuthenticationHeader(const QByteArray &authentication)
124{
125 authenticationHeader = authentication;
126}
127
128void Http2Server::setRedirect(const QByteArray &url, int count)
129{
130 redirectUrl = url;
131 redirectCount = count;
132}
133
134void Http2Server::emulateGOAWAY(int timeout)
135{
136 Q_ASSERT(timeout >= 0);
137 testingGOAWAY = true;
138 goawayTimeout = timeout;
139}
140
141void Http2Server::redirectOpenStream(quint16 port)
142{
143 redirectWhileReading = true;
144 targetPort = port;
145}
146
147bool Http2Server::isClearText() const
148{
149 return connectionType == H2Type::h2c || connectionType == H2Type::h2cDirect;
150}
151
152QByteArray Http2Server::requestAuthorizationHeader()
153{
154 const auto isAuthHeader = [](const HeaderField &field) {
155 return field.name == "authorization";
156 };
157 const auto requestHeaders = decoder.decodedHeader();
158 const auto authentication =
159 std::find_if(first: requestHeaders.cbegin(), last: requestHeaders.cend(), pred: isAuthHeader);
160 return authentication == requestHeaders.cend() ? QByteArray() : authentication->value;
161}
162
163void Http2Server::startServer()
164{
165 if (listen()) {
166 if (isClearText())
167 authority = QStringLiteral("127.0.0.1:%1").arg(a: serverPort()).toLatin1();
168 emit serverStarted(port: serverPort());
169 }
170}
171
172bool Http2Server::sendProtocolSwitchReply()
173{
174 Q_ASSERT(socket);
175 Q_ASSERT(connectionType == H2Type::h2c);
176 // The first and the last HTTP/1.1 response we send:
177 const char response[] = "HTTP/1.1 101 Switching Protocols\r\n"
178 "Connection: Upgrade\r\n"
179 "Upgrade: h2c\r\n\r\n";
180 const qint64 size = sizeof response - 1;
181 return socket->write(data: response, len: size) == size;
182}
183
184void Http2Server::sendServerSettings()
185{
186 Q_ASSERT(socket);
187
188 if (!serverSettings.size())
189 return;
190
191 writer.start(type: FrameType::SETTINGS, flags: FrameFlag::EMPTY, streamID: connectionStreamID);
192 for (auto it = serverSettings.cbegin(); it != serverSettings.cend(); ++it) {
193 writer.append(identifier: it.key());
194 writer.append(val: it.value());
195 if (it.key() == Settings::INITIAL_WINDOW_SIZE_ID)
196 streamRecvWindowSize = it.value();
197 }
198 writer.write(socket&: *socket);
199 // Now, let's update our peer on a session recv window size:
200 const quint32 updatedSize = 10 * streamRecvWindowSize;
201 if (sessionRecvWindowSize < updatedSize) {
202 const quint32 delta = updatedSize - sessionRecvWindowSize;
203 sessionRecvWindowSize = updatedSize;
204 sessionCurrRecvWindow = updatedSize;
205 sendWINDOW_UPDATE(streamID: connectionStreamID, delta);
206 }
207
208 waitingClientAck = true;
209 settingsSent = true;
210}
211
212void Http2Server::sendGOAWAY(quint32 streamID, quint32 error, quint32 lastStreamID)
213{
214 Q_ASSERT(socket);
215
216 writer.start(type: FrameType::GOAWAY, flags: FrameFlag::EMPTY, streamID);
217 writer.append(val: lastStreamID);
218 writer.append(val: error);
219 writer.write(socket&: *socket);
220}
221
222void Http2Server::sendRST_STREAM(quint32 streamID, quint32 error)
223{
224 Q_ASSERT(socket);
225
226 writer.start(type: FrameType::RST_STREAM, flags: FrameFlag::EMPTY, streamID);
227 writer.append(val: error);
228 writer.write(socket&: *socket);
229}
230
231void Http2Server::sendDATA(quint32 streamID, quint32 windowSize)
232{
233 Q_ASSERT(socket);
234
235 const auto it = suspendedStreams.find(x: streamID);
236 Q_ASSERT(it != suspendedStreams.end());
237
238 const quint32 offset = it->second;
239 Q_ASSERT(offset < quint32(responseBody.size()));
240
241 quint32 bytesToSend = std::min<quint32>(a: windowSize, b: responseBody.size() - offset);
242 quint32 bytesSent = 0;
243 const quint32 frameSizeLimit(clientSetting(identifier: Settings::MAX_FRAME_SIZE_ID, defaultValue: Http2::minPayloadLimit));
244 const uchar *src = reinterpret_cast<const uchar *>(responseBody.constData() + offset);
245 const bool last = offset + bytesToSend == quint32(responseBody.size());
246
247 // The payload can significantly exceed frameSizeLimit. Internally, writer
248 // will do needed fragmentation, but if some test failed, there is no need
249 // to wait for writer to send all DATA frames, we check 'interrupted' and
250 // stop early instead.
251 const quint32 framesInChunk = 10;
252 while (bytesToSend) {
253 if (interrupted.loadAcquire())
254 return;
255 const quint32 chunkSize = std::min<quint32>(a: framesInChunk * frameSizeLimit, b: bytesToSend);
256 writer.start(type: FrameType::DATA, flags: FrameFlag::EMPTY, streamID);
257 writer.writeDATA(socket&: *socket, sizeLimit: frameSizeLimit, src, size: chunkSize);
258 src += chunkSize;
259 bytesToSend -= chunkSize;
260 bytesSent += chunkSize;
261 if (frameSizeLimit != Http2::minPayloadLimit) {
262 // Our test is probably interested in how many DATA frames were sent.
263 emit sendingData();
264 }
265 }
266
267 if (interrupted.loadAcquire())
268 return;
269
270 if (last) {
271 writer.start(type: FrameType::DATA, flags: FrameFlag::END_STREAM, streamID);
272 writer.setPayloadSize(0);
273 writer.write(socket&: *socket);
274 suspendedStreams.erase(position: it);
275 activeRequests.erase(x: streamID);
276
277 Q_ASSERT(closedStreams.find(streamID) == closedStreams.end());
278 closedStreams.insert(x: streamID);
279 } else {
280 it->second += bytesSent;
281 }
282}
283
284void Http2Server::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
285{
286 Q_ASSERT(socket);
287
288 writer.start(type: FrameType::WINDOW_UPDATE, flags: FrameFlag::EMPTY, streamID);
289 writer.append(val: delta);
290 writer.write(socket&: *socket);
291}
292
293void Http2Server::incomingConnection(qintptr socketDescriptor)
294{
295 if (isClearText()) {
296 socket.reset(other: new QTcpSocket);
297 const bool set = socket->setSocketDescriptor(socketDescriptor);
298 Q_ASSERT(set);
299 // Stop listening:
300 close();
301 upgradeProtocol = connectionType == H2Type::h2c;
302 QMetaObject::invokeMethod(obj: this, member: "connectionEstablished",
303 type: Qt::QueuedConnection);
304 } else {
305#if QT_CONFIG(ssl)
306 socket.reset(other: new QSslSocket);
307 QSslSocket *sslSocket = static_cast<QSslSocket *>(socket.data());
308
309 if (connectionType == H2Type::h2Alpn) {
310 // Add HTTP2 as supported protocol:
311 auto conf = QSslConfiguration::defaultConfiguration();
312 auto protos = conf.allowedNextProtocols();
313 protos.prepend(t: QSslConfiguration::ALPNProtocolHTTP2);
314 conf.setAllowedNextProtocols(protos);
315 sslSocket->setSslConfiguration(conf);
316 }
317 // SSL-related setup ...
318 sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone);
319 sslSocket->setProtocol(QSsl::TlsV1_2OrLater);
320 connect(sender: sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
321 receiver: this, SLOT(ignoreErrorSlot()));
322 QFile file(SRCDIR "certs/fluke.key");
323 file.open(flags: QIODevice::ReadOnly);
324 QSslKey key(file.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
325 sslSocket->setPrivateKey(key);
326 auto localCert = QSslCertificate::fromPath(SRCDIR "certs/fluke.cert");
327 sslSocket->setLocalCertificateChain(localCert);
328 sslSocket->setSocketDescriptor(socketDescriptor, state: QAbstractSocket::ConnectedState);
329 // Stop listening.
330 close();
331 // Start SSL handshake and ALPN:
332 connect(sender: sslSocket, SIGNAL(encrypted()), receiver: this, SLOT(connectionEstablished()));
333 sslSocket->startServerEncryption();
334#else
335 Q_ASSERT(0);
336#endif
337 }
338}
339
340quint32 Http2Server::clientSetting(Http2::Settings identifier, quint32 defaultValue)
341{
342 const auto it = expectedClientSettings.find(akey: identifier);
343 if (it != expectedClientSettings.end())
344 return it.value();
345 return defaultValue;
346}
347
348bool Http2Server::readMethodLine()
349{
350 // We know for sure that Qt did the right thing sending us the correct
351 // Request-line with CRLF at the end ...
352 // We're overly simplistic here but all we need to know - the method.
353 while (socket->bytesAvailable()) {
354 char c = 0;
355 if (socket->read(data: &c, maxlen: 1) != 1)
356 return false;
357 if (c == '\n' && requestLine.endsWith(c: '\r')) {
358 if (requestLine.startsWith(c: "GET"))
359 requestType = QHttpNetworkRequest::Get;
360 else if (requestLine.startsWith(c: "POST"))
361 requestType = QHttpNetworkRequest::Post;
362 else
363 requestType = QHttpNetworkRequest::Custom; // 'invalid'.
364 requestLine.clear();
365
366 return true;
367 } else {
368 requestLine.append(c);
369 }
370 }
371
372 return false;
373}
374
375bool Http2Server::verifyProtocolUpgradeRequest()
376{
377 Q_ASSERT(protocolUpgradeHandler.data());
378
379 bool connectionOk = false;
380 bool upgradeOk = false;
381 bool settingsOk = false;
382
383 QHttpNetworkReplyPrivate *firstRequestReader = protocolUpgradeHandler->d_func();
384
385 // That's how we append them, that's what I expect to find:
386 for (const auto &header : firstRequestReader->fields) {
387 if (header.first == "Connection")
388 connectionOk = header.second.contains(c: "Upgrade, HTTP2-Settings");
389 else if (header.first == "Upgrade")
390 upgradeOk = header.second.contains(c: "h2c");
391 else if (header.first == "HTTP2-Settings")
392 settingsOk = true;
393 }
394
395 return connectionOk && upgradeOk && settingsOk;
396}
397
398void Http2Server::triggerGOAWAYEmulation()
399{
400 Q_ASSERT(testingGOAWAY);
401 auto timer = new QTimer(this);
402 timer->setSingleShot(true);
403 connect(sender: timer, signal: &QTimer::timeout, slot: [this]() {
404 sendGOAWAY(streamID: quint32(connectionStreamID), error: quint32(INTERNAL_ERROR), lastStreamID: 0);
405 });
406 timer->start(msec: goawayTimeout);
407}
408
409void Http2Server::connectionEstablished()
410{
411 using namespace Http2;
412
413 if (testingGOAWAY && !isClearText())
414 return triggerGOAWAYEmulation();
415
416 // For clearTextHTTP2 we first have to respond with 'protocol switch'
417 // and then continue with whatever logic we have (testingGOAWAY or not),
418 // otherwise our 'peer' cannot process HTTP/2 frames yet.
419
420 connect(sender: socket.data(), SIGNAL(readyRead()),
421 receiver: this, SLOT(readReady()));
422
423 waitingClientPreface = true;
424 waitingClientAck = false;
425 waitingClientSettings = false;
426 settingsSent = false;
427
428 if (connectionType == H2Type::h2c) {
429 requestLine.clear();
430 // Now we have to handle HTTP/1.1 request. We use Get/Post in our test,
431 // so set requestType to something unsupported:
432 requestType = QHttpNetworkRequest::Options;
433 } else {
434 // We immediately send our settings so that our client
435 // can use flow control correctly.
436 sendServerSettings();
437 }
438
439 if (socket->bytesAvailable())
440 readReady();
441}
442
443void Http2Server::ignoreErrorSlot()
444{
445#ifndef QT_NO_SSL
446 static_cast<QSslSocket *>(socket.data())->ignoreSslErrors();
447#endif
448}
449
450// Now HTTP2 "server" part:
451/*
452This code is overly simplified but it tests the basic HTTP2 expected behavior:
4531. CONNECTION PREFACE
4542. SETTINGS
4553. sends our own settings (to modify the flow control)
4564. collects and reports requests
4575. if asked - sends responds to those requests
4586. does some very basic error handling
4597. tests frames validity/stream logic at the very basic level.
460*/
461
462void Http2Server::readReady()
463{
464 if (connectionError)
465 return;
466
467 if (redirectSent) {
468 // We are a "single shot" server, working in 'h2' mode,
469 // responding with a redirect code. Don't bother to handle
470 // anything else now.
471 return;
472 }
473
474 if (upgradeProtocol) {
475 handleProtocolUpgrade();
476 } else if (waitingClientPreface) {
477 handleConnectionPreface();
478 } else {
479 const auto status = reader.read(socket&: *socket);
480 switch (status) {
481 case FrameStatus::incompleteFrame:
482 break;
483 case FrameStatus::goodFrame:
484 handleIncomingFrame();
485 break;
486 default:
487 connectionError = true;
488 sendGOAWAY(streamID: connectionStreamID, error: PROTOCOL_ERROR, lastStreamID: connectionStreamID);
489 }
490 }
491
492 if (socket->bytesAvailable())
493 QMetaObject::invokeMethod(obj: this, member: "readReady", type: Qt::QueuedConnection);
494}
495
496void Http2Server::handleProtocolUpgrade()
497{
498 using ReplyPrivate = QHttpNetworkReplyPrivate;
499 Q_ASSERT(upgradeProtocol);
500
501 if (!protocolUpgradeHandler.data())
502 protocolUpgradeHandler.reset(other: new Http11Reply);
503
504 QHttpNetworkReplyPrivate *firstRequestReader = protocolUpgradeHandler->d_func();
505
506 // QHttpNetworkReplyPrivate parses ... reply. It will, unfortunately, fail
507 // on the first line ... which is a part of request. So we read this line
508 // and extract the method first.
509 if (firstRequestReader->state == ReplyPrivate::NothingDoneState) {
510 if (!readMethodLine())
511 return;
512
513 if (requestType != QHttpNetworkRequest::Get && requestType != QHttpNetworkRequest::Post) {
514 emit invalidRequest(streamID: 1);
515 return;
516 }
517
518 firstRequestReader->state = ReplyPrivate::ReadingHeaderState;
519 }
520
521 if (!socket->bytesAvailable())
522 return;
523
524 if (firstRequestReader->state == ReplyPrivate::ReadingHeaderState)
525 firstRequestReader->readHeader(socket: socket.data());
526 else if (firstRequestReader->state == ReplyPrivate::ReadingDataState)
527 firstRequestReader->readBodyFast(socket: socket.data(), rb: &firstRequestReader->responseData);
528
529 switch (firstRequestReader->state) {
530 case ReplyPrivate::ReadingHeaderState:
531 return;
532 case ReplyPrivate::ReadingDataState:
533 if (requestType == QHttpNetworkRequest::Post)
534 return;
535 break;
536 case ReplyPrivate::AllDoneState:
537 break;
538 default:
539 socket->close();
540 return;
541 }
542
543 if (!verifyProtocolUpgradeRequest() || !sendProtocolSwitchReply()) {
544 socket->close();
545 return;
546 }
547
548 upgradeProtocol = false;
549 protocolUpgradeHandler.reset(other: nullptr);
550
551 if (testingGOAWAY)
552 return triggerGOAWAYEmulation();
553
554 // HTTP/1.1 'fields' we have in firstRequestRead are useless (they are not
555 // even allowed in HTTP/2 header). Let's pretend we have received
556 // valid HTTP/2 headers and can extract fields we need:
557 HttpHeader h2header;
558 h2header.push_back(x: HeaderField(":scheme", "http")); // we are in clearTextHTTP2 mode.
559 h2header.push_back(x: HeaderField(":authority", authority));
560 activeRequests[1] = std::move(h2header);
561 // After protocol switch we immediately send our SETTINGS.
562 sendServerSettings();
563 if (requestType == QHttpNetworkRequest::Get)
564 emit receivedRequest(streamID: 1);
565 else
566 emit receivedData(streamID: 1);
567}
568
569void Http2Server::handleConnectionPreface()
570{
571 Q_ASSERT(waitingClientPreface);
572
573 if (socket->bytesAvailable() < clientPrefaceLength)
574 return; // Wait for more data ...
575
576 char buf[clientPrefaceLength] = {};
577 socket->read(data: buf, maxlen: clientPrefaceLength);
578 if (std::memcmp(s1: buf, s2: Http2clientPreface, n: clientPrefaceLength)) {
579 sendGOAWAY(streamID: connectionStreamID, error: PROTOCOL_ERROR, lastStreamID: connectionStreamID);
580 emit clientPrefaceError();
581 connectionError = true;
582 return;
583 }
584
585 waitingClientPreface = false;
586 waitingClientSettings = true;
587}
588
589void Http2Server::handleIncomingFrame()
590{
591 // Frames that our implementation can send include:
592 // 1. SETTINGS (happens only during connection preface,
593 // handled already by this point)
594 // 2. SETTIGNS with ACK should be sent only as a response
595 // to a server's SETTINGS
596 // 3. HEADERS
597 // 4. CONTINUATION
598 // 5. DATA
599 // 6. PING
600 // 7. RST_STREAM
601 // 8. GOAWAY
602
603 if (testingGOAWAY) {
604 // GOAWAY test is simplistic for now: after HTTP/2 was
605 // negotiated (via ALPN/NPN or a protocol switch), send
606 // a GOAWAY frame after some (probably non-zero) timeout.
607 // We do not handle any frames, but timeout gives QNAM
608 // more time to initiate more streams and thus make the
609 // test more interesting/complex (on a client side).
610 return;
611 }
612
613 inboundFrame = std::move(reader.inboundFrame());
614
615 if (continuedRequest.size()) {
616 if (inboundFrame.type() != FrameType::CONTINUATION ||
617 inboundFrame.streamID() != continuedRequest.front().streamID()) {
618 sendGOAWAY(streamID: connectionStreamID, error: PROTOCOL_ERROR, lastStreamID: connectionStreamID);
619 emit invalidFrame();
620 connectionError = true;
621 return;
622 }
623 }
624
625 switch (inboundFrame.type()) {
626 case FrameType::SETTINGS:
627 handleSETTINGS();
628 break;
629 case FrameType::HEADERS:
630 case FrameType::CONTINUATION:
631 continuedRequest.push_back(x: std::move(inboundFrame));
632 processRequest();
633 break;
634 case FrameType::DATA:
635 handleDATA();
636 break;
637 case FrameType::RST_STREAM:
638 // TODO: this is not tested for now.
639 break;
640 case FrameType::PING:
641 // TODO: this is not tested for now.
642 break;
643 case FrameType::GOAWAY:
644 // TODO: this is not tested for now.
645 break;
646 case FrameType::WINDOW_UPDATE:
647 handleWINDOW_UPDATE();
648 break;
649 default:;
650 }
651}
652
653void Http2Server::handleSETTINGS()
654{
655 // SETTINGS is either a part of the connection preface,
656 // or a SETTINGS ACK.
657 Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS);
658
659 if (inboundFrame.flags().testFlag(flag: FrameFlag::ACK)) {
660 if (!waitingClientAck || inboundFrame.dataSize()) {
661 emit invalidFrame();
662 connectionError = true;
663 waitingClientAck = false;
664 return;
665 }
666
667 waitingClientAck = false;
668 emit serverSettingsAcked();
669 return;
670 }
671
672 // QHttp2ProtocolHandler always sends some settings,
673 // and the size is a multiple of 6.
674 if (!inboundFrame.dataSize() || inboundFrame.dataSize() % 6) {
675 sendGOAWAY(streamID: connectionStreamID, error: FRAME_SIZE_ERROR, lastStreamID: connectionStreamID);
676 emit clientPrefaceError();
677 connectionError = true;
678 return;
679 }
680
681 const uchar *src = inboundFrame.dataBegin();
682 const uchar *end = src + inboundFrame.dataSize();
683
684 const auto notFound = expectedClientSettings.end();
685
686 while (src != end) {
687 const auto id = Http2::Settings(qFromBigEndian<quint16>(src));
688 const auto value = qFromBigEndian<quint32>(src: src + 2);
689 if (expectedClientSettings.find(akey: id) == notFound ||
690 expectedClientSettings[id] != value) {
691 emit clientPrefaceError();
692 connectionError = true;
693 return;
694 }
695
696 src += 6;
697 }
698
699 // Send SETTINGS ACK:
700 writer.start(type: FrameType::SETTINGS, flags: FrameFlag::ACK, streamID: connectionStreamID);
701 writer.write(socket&: *socket);
702 waitingClientSettings = false;
703 emit clientPrefaceOK();
704}
705
706void Http2Server::handleDATA()
707{
708 Q_ASSERT(inboundFrame.type() == FrameType::DATA);
709
710 const auto streamID = inboundFrame.streamID();
711
712 if (!is_valid_client_stream(streamID) ||
713 closedStreams.find(x: streamID) != closedStreams.end()) {
714 emit invalidFrame();
715 connectionError = true;
716 sendGOAWAY(streamID: connectionStreamID, error: PROTOCOL_ERROR, lastStreamID: connectionStreamID);
717 return;
718 }
719
720 const auto payloadSize = inboundFrame.payloadSize();
721 if (sessionCurrRecvWindow < payloadSize) {
722 // Client does not respect our session window size!
723 emit invalidRequest(streamID);
724 connectionError = true;
725 sendGOAWAY(streamID: connectionStreamID, error: FLOW_CONTROL_ERROR, lastStreamID: connectionStreamID);
726 return;
727 }
728
729 auto it = streamWindows.find(x: streamID);
730 if (it == streamWindows.end())
731 it = streamWindows.insert(x: std::make_pair(x: streamID, y&: streamRecvWindowSize)).first;
732
733
734 if (it->second < payloadSize) {
735 emit invalidRequest(streamID);
736 connectionError = true;
737 sendGOAWAY(streamID: connectionStreamID, error: FLOW_CONTROL_ERROR, lastStreamID: connectionStreamID);
738 return;
739 }
740
741 it->second -= payloadSize;
742 if (it->second < streamRecvWindowSize / 2) {
743 sendWINDOW_UPDATE(streamID, delta: streamRecvWindowSize / 2);
744 it->second += streamRecvWindowSize / 2;
745 }
746
747 sessionCurrRecvWindow -= payloadSize;
748
749 if (sessionCurrRecvWindow < sessionRecvWindowSize / 2) {
750 // This is some quite naive and trivial logic on when to update.
751
752 sendWINDOW_UPDATE(streamID: connectionStreamID, delta: sessionRecvWindowSize / 2);
753 sessionCurrRecvWindow += sessionRecvWindowSize / 2;
754 }
755
756 if (inboundFrame.flags().testFlag(flag: FrameFlag::END_STREAM)) {
757 if (responseBody.isEmpty()) {
758 closedStreams.insert(x: streamID); // Enter "half-closed remote" state.
759 streamWindows.erase(position: it);
760 }
761 emit receivedData(streamID);
762 }
763 emit receivedDATAFrame(streamID,
764 body: QByteArray(reinterpret_cast<const char *>(inboundFrame.dataBegin()),
765 inboundFrame.dataSize()));
766}
767
768void Http2Server::handleWINDOW_UPDATE()
769{
770 const auto streamID = inboundFrame.streamID();
771 if (!streamID) // We ignore this for now to keep things simple.
772 return;
773
774 if (streamID && suspendedStreams.find(x: streamID) == suspendedStreams.end()) {
775 if (closedStreams.find(x: streamID) == closedStreams.end()) {
776 sendRST_STREAM(streamID, error: PROTOCOL_ERROR);
777 emit invalidFrame();
778 connectionError = true;
779 }
780
781 return;
782 }
783
784 const quint32 delta = qFromBigEndian<quint32>(src: inboundFrame.dataBegin());
785 if (!delta || delta > quint32(std::numeric_limits<qint32>::max())) {
786 sendRST_STREAM(streamID, error: PROTOCOL_ERROR);
787 emit invalidFrame();
788 connectionError = true;
789 return;
790 }
791
792 emit windowUpdate(streamID);
793 sendDATA(streamID, windowSize: delta);
794}
795
796void Http2Server::sendResponse(quint32 streamID, bool emptyBody)
797{
798 Q_ASSERT(activeRequests.find(streamID) != activeRequests.end());
799
800 const quint32 maxFrameSize(clientSetting(identifier: Settings::MAX_FRAME_SIZE_ID,
801 defaultValue: Http2::maxPayloadSize));
802
803 if (pushPromiseEnabled) {
804 // A real server supporting PUSH_PROMISE will probably first send
805 // PUSH_PROMISE and then a normal response (to a real request),
806 // so that a client parsing this response and discovering another
807 // resource it needs, will _already_ have this additional resource
808 // in PUSH_PROMISE.
809 lastPromisedStream += 2;
810
811 writer.start(type: FrameType::PUSH_PROMISE, flags: FrameFlag::END_HEADERS, streamID);
812 writer.append(val: lastPromisedStream);
813
814 HttpHeader pushHeader;
815 fill_push_header(originalRequest: activeRequests[streamID], promisedRequest&: pushHeader);
816 pushHeader.push_back(x: HeaderField(":method", "GET"));
817 pushHeader.push_back(x: HeaderField(":path", pushPath));
818
819 // Now interesting part, let's make it into 'stream':
820 activeRequests[lastPromisedStream] = pushHeader;
821
822 HPack::BitOStream ostream(writer.outboundFrame().buffer);
823 const bool result = encoder.encodeRequest(outputStream&: ostream, header: pushHeader);
824 Q_ASSERT(result);
825
826 // Well, it's not HEADERS, it's PUSH_PROMISE with ... HEADERS block.
827 // Should work.
828 writer.writeHEADERS(socket&: *socket, sizeLimit: maxFrameSize);
829 qDebug() << "server sent a PUSH_PROMISE on" << lastPromisedStream;
830
831 if (responseBody.isEmpty())
832 responseBody = QByteArray("I PROMISE (AND PUSH) YOU ...");
833
834 // Now we send this promised data as a normal response on our reserved
835 // stream (disabling PUSH_PROMISE for the moment to avoid recursion):
836 pushPromiseEnabled = false;
837 sendResponse(streamID: lastPromisedStream, emptyBody: false);
838 pushPromiseEnabled = true;
839 // Now we'll continue with _normal_ response.
840 }
841
842 writer.start(type: FrameType::HEADERS, flags: FrameFlag::END_HEADERS, streamID);
843 if (emptyBody)
844 writer.addFlag(flag: FrameFlag::END_STREAM);
845
846 // We assume any auth is correct. Leaves the checking to the test itself
847 const bool hasAuth = !requestAuthorizationHeader().isEmpty();
848
849 HttpHeader header;
850 if (redirectWhileReading) {
851 if (redirectSent) {
852 // This is a "single-shot" server responding with a redirect code.
853 return;
854 }
855
856 redirectSent = true;
857
858 qDebug(msg: "server received HEADERS frame (followed by DATA frames), redirecting ...");
859 Q_ASSERT(targetPort);
860 header.push_back(x: {":status", "308"});
861 const QString url("%1://localhost:%2/");
862 header.push_back(x: {"location", url.arg(args: isClearText() ? QStringLiteral("http") : QStringLiteral("https"),
863 args: QString::number(targetPort)).toLatin1()});
864 } else if (redirectCount > 0) { // Not redirecting while reading, unlike above
865 --redirectCount;
866 header.push_back(x: {":status", "308"});
867 header.push_back(x: {"location", redirectUrl});
868 } else if (!authenticationHeader.isEmpty() && !hasAuth) {
869 header.push_back(x: { ":status", "401" });
870 header.push_back(x: HPack::HeaderField("www-authenticate", authenticationHeader));
871 authenticationHeader.clear();
872 } else {
873 header.push_back(x: {":status", "200"});
874 }
875
876 if (!emptyBody) {
877 header.push_back(x: HPack::HeaderField("content-length",
878 QString("%1").arg(a: responseBody.size()).toLatin1()));
879 }
880
881 HPack::BitOStream ostream(writer.outboundFrame().buffer);
882 const bool result = encoder.encodeResponse(outputStream&: ostream, header);
883 Q_ASSERT(result);
884
885 writer.writeHEADERS(socket&: *socket, sizeLimit: maxFrameSize);
886
887 if (!emptyBody) {
888 Q_ASSERT(suspendedStreams.find(streamID) == suspendedStreams.end());
889
890 const quint32 windowSize = clientSetting(identifier: Settings::INITIAL_WINDOW_SIZE_ID,
891 defaultValue: Http2::defaultSessionWindowSize);
892 // Suspend to immediately resume it.
893 suspendedStreams[streamID] = 0; // start sending from offset 0
894 sendDATA(streamID, windowSize);
895 } else {
896 activeRequests.erase(x: streamID);
897 closedStreams.insert(x: streamID);
898 }
899}
900
901void Http2Server::stopSendingDATAFrames()
902{
903 interrupted.storeRelease(newValue: 1);
904}
905
906void Http2Server::processRequest()
907{
908 Q_ASSERT(continuedRequest.size());
909
910 if (!continuedRequest.back().flags().testFlag(flag: FrameFlag::END_HEADERS))
911 return;
912
913 // We test here:
914 // 1. stream is 'idle'.
915 // 2. has priority set and dependency (it's 0x0 at the moment).
916 // 3. header can be decompressed.
917 const auto &headersFrame = continuedRequest.front();
918 const auto streamID = headersFrame.streamID();
919 if (!is_valid_client_stream(streamID)) {
920 emit invalidRequest(streamID);
921 connectionError = true;
922 sendGOAWAY(streamID: connectionStreamID, error: PROTOCOL_ERROR, lastStreamID: connectionStreamID);
923 return;
924 }
925
926 if (closedStreams.find(x: streamID) != closedStreams.end()) {
927 emit invalidFrame();
928 connectionError = true;
929 sendGOAWAY(streamID: connectionStreamID, error: PROTOCOL_ERROR, lastStreamID: connectionStreamID);
930 return;
931 }
932
933 quint32 dep = 0;
934 uchar w = 0;
935 if (!headersFrame.priority(streamID: &dep, weight: &w)) {
936 emit invalidFrame();
937 sendRST_STREAM(streamID, error: PROTOCOL_ERROR);
938 return;
939 }
940
941 // Assemble headers ...
942 quint32 totalSize = 0;
943 for (const auto &frame : continuedRequest) {
944 if (std::numeric_limits<quint32>::max() - frame.dataSize() < totalSize) {
945 // Resulted in overflow ...
946 emit invalidFrame();
947 connectionError = true;
948 sendGOAWAY(streamID: connectionStreamID, error: PROTOCOL_ERROR, lastStreamID: connectionStreamID);
949 return;
950 }
951 totalSize += frame.dataSize();
952 }
953
954 std::vector<uchar> hpackBlock(totalSize);
955 auto dst = hpackBlock.begin();
956 for (const auto &frame : continuedRequest) {
957 if (!frame.dataSize())
958 continue;
959 std::copy(first: frame.dataBegin(), last: frame.dataBegin() + frame.dataSize(), result: dst);
960 dst += frame.dataSize();
961 }
962
963 HPack::BitIStream inputStream{&hpackBlock[0], &hpackBlock[0] + hpackBlock.size()};
964
965 if (!decoder.decodeHeaderFields(inputStream)) {
966 emit decompressionFailed(streamID);
967 sendRST_STREAM(streamID, error: COMPRESSION_ERROR);
968 closedStreams.insert(x: streamID);
969 return;
970 }
971
972 // Actually, if needed, we can do a comparison here.
973 activeRequests[streamID] = decoder.decodedHeader();
974 if (headersFrame.flags().testFlag(flag: FrameFlag::END_STREAM))
975 emit receivedRequest(streamID);
976
977 if (redirectWhileReading) {
978 sendResponse(streamID, emptyBody: true);
979 // Don't try to read any DATA frames ...
980 socket->disconnect();
981 } // else - we're waiting for incoming DATA frames ...
982
983 continuedRequest.clear();
984}
985
986QT_END_NAMESPACE
987

source code of qtbase/tests/auto/network/access/http2/http2srv.cpp