| 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 | |