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 | |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | using namespace Http2; |
56 | using namespace HPack; |
57 | |
58 | namespace |
59 | { |
60 | |
61 | inline 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 | |
67 | void (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 | |
79 | Http2Server::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 | |
108 | Http2Server::~Http2Server() |
109 | { |
110 | } |
111 | |
112 | void Http2Server::enablePushPromise(bool pushEnabled, const QByteArray &path) |
113 | { |
114 | pushPromiseEnabled = pushEnabled; |
115 | pushPath = path; |
116 | } |
117 | |
118 | void Http2Server::setResponseBody(const QByteArray &body) |
119 | { |
120 | responseBody = body; |
121 | } |
122 | |
123 | void Http2Server::(const QByteArray &authentication) |
124 | { |
125 | authenticationHeader = authentication; |
126 | } |
127 | |
128 | void Http2Server::setRedirect(const QByteArray &url, int count) |
129 | { |
130 | redirectUrl = url; |
131 | redirectCount = count; |
132 | } |
133 | |
134 | void Http2Server::emulateGOAWAY(int timeout) |
135 | { |
136 | Q_ASSERT(timeout >= 0); |
137 | testingGOAWAY = true; |
138 | goawayTimeout = timeout; |
139 | } |
140 | |
141 | void Http2Server::redirectOpenStream(quint16 port) |
142 | { |
143 | redirectWhileReading = true; |
144 | targetPort = port; |
145 | } |
146 | |
147 | bool Http2Server::isClearText() const |
148 | { |
149 | return connectionType == H2Type::h2c || connectionType == H2Type::h2cDirect; |
150 | } |
151 | |
152 | QByteArray Http2Server::() |
153 | { |
154 | const auto = [](const HeaderField &field) { |
155 | return field.name == "authorization" ; |
156 | }; |
157 | const auto = 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 | |
163 | void 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 | |
172 | bool 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 | |
184 | void 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 | |
212 | void 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 | |
222 | void 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 | |
231 | void 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 | |
284 | void 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 | |
293 | void 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 | |
340 | quint32 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 | |
348 | bool 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 | |
375 | bool 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 & : 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 | |
398 | void 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 | |
409 | void 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 | |
443 | void 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 | /* |
452 | This code is overly simplified but it tests the basic HTTP2 expected behavior: |
453 | 1. CONNECTION PREFACE |
454 | 2. SETTINGS |
455 | 3. sends our own settings (to modify the flow control) |
456 | 4. collects and reports requests |
457 | 5. if asked - sends responds to those requests |
458 | 6. does some very basic error handling |
459 | 7. tests frames validity/stream logic at the very basic level. |
460 | */ |
461 | |
462 | void 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 | |
496 | void 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 ; |
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 | |
569 | void 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 | |
589 | void 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 | |
653 | void 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 | |
706 | void 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 | |
768 | void 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 | |
796 | void 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 ; |
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 ; |
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 | |
901 | void Http2Server::stopSendingDATAFrames() |
902 | { |
903 | interrupted.storeRelease(newValue: 1); |
904 | } |
905 | |
906 | void 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 & = 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 | |
986 | QT_END_NAMESPACE |
987 | |