Coverage Report

Created: 2026-10-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/network/access/qhttp2protocolhandler.cpp
Line
Count
Source
1
// Copyright (C) 2016 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
// Qt-Security score:critical reason:network-protocol
4
5
#include "access/http2/http2protocol_p.h"
6
#include "access/qhttp2connection_p.h"
7
#include "qhttpnetworkconnection_p.h"
8
#include "qhttp2protocolhandler_p.h"
9
10
#include "http2/http2frames_p.h"
11
12
#include <private/qnoncontiguousbytedevice_p.h>
13
#include <private/qsocketabstraction_p.h>
14
15
#include <QtNetwork/qabstractsocket.h>
16
17
#include <QtCore/qloggingcategory.h>
18
#include <QtCore/qendian.h>
19
#include <QtCore/qdebug.h>
20
#include <QtCore/qlist.h>
21
#include <QtCore/qnumeric.h>
22
#include <QtCore/qurl.h>
23
24
#include <qhttp2configuration.h>
25
26
#ifndef QT_NO_NETWORKPROXY
27
#  include <QtNetwork/qnetworkproxy.h>
28
#endif
29
30
#include <qcoreapplication.h>
31
32
#include <algorithm>
33
#include <vector>
34
#include <optional>
35
36
QT_BEGIN_NAMESPACE
37
38
using namespace Qt::StringLiterals;
39
40
namespace
41
{
42
43
HPack::HttpHeader build_headers(const QHttpNetworkRequest &request, quint32 maxHeaderListSize,
44
                                bool useProxy)
45
0
{
46
0
    using namespace HPack;
47
48
0
    HttpHeader header;
49
0
    header.reserve(300);
50
51
    // 1. Before anything - mandatory fields, if they do not fit into maxHeaderList -
52
    // then stop immediately with error.
53
0
    const auto auth = request.url().authority(QUrl::FullyEncoded | QUrl::RemoveUserInfo).toLatin1();
54
0
    header.emplace_back(":authority", auth);
55
0
    header.emplace_back(":method", request.methodName());
56
0
    header.emplace_back(":path", request.uri(useProxy));
57
0
    header.emplace_back(":scheme", request.url().scheme().toLatin1());
58
59
0
    HeaderSize size = header_size(header);
60
0
    if (!size.first) // Ooops!
61
0
        return HttpHeader();
62
63
0
    if (size.second > maxHeaderListSize)
64
0
        return HttpHeader(); // Bad, we cannot send this request ...
65
66
0
    const QHttpHeaders requestHeader = request.header();
67
0
    using WK = QHttpHeaders::WellKnownHeader;
68
0
    for (qsizetype i = 0; i < requestHeader.size(); ++i) {
69
0
        const auto name = requestHeader.nameAt(i);
70
0
        const auto value = requestHeader.valueAt(i);
71
0
        const HeaderSize delta = entry_size(name, value);
72
0
        if (!delta.first) // Overflow???
73
0
            break;
74
0
        if (std::numeric_limits<quint32>::max() - delta.second < size.second)
75
0
            break;
76
0
        size.second += delta.second;
77
0
        if (size.second > maxHeaderListSize)
78
0
            break;
79
80
0
        if (name == QHttpHeaders::wellKnownHeaderName(WK::Connection)
81
0
            || name == QHttpHeaders::wellKnownHeaderName(WK::Host)
82
0
            || name == QHttpHeaders::wellKnownHeaderName(WK::KeepAlive)
83
0
            || name.compare("proxy-connection"_L1, Qt::CaseInsensitive) == 0
84
0
            || name == QHttpHeaders::wellKnownHeaderName(WK::TransferEncoding)) {
85
0
            continue; // Those headers are not valid (section 3.2.1) - from QSpdyProtocolHandler
86
0
        }
87
        // TODO: verify with specs, which fields are valid to send ....
88
        //
89
        // Note: RFC 7450 8.1.2 (HTTP/2) states that header field names must be lower-cased
90
        // prior to their encoding in HTTP/2
91
0
        header.emplace_back(QByteArray{name.data(), name.size()}.toLower(),
92
0
                            QByteArray{value.data(), value.size()});
93
0
    }
94
95
0
    return header;
96
0
}
97
98
QUrl urlkey_from_request(const QHttpNetworkRequest &request)
99
0
{
100
0
    QUrl url;
101
102
0
    url.setScheme(request.url().scheme());
103
0
    url.setAuthority(request.url().authority(QUrl::FullyEncoded | QUrl::RemoveUserInfo));
104
0
    url.setPath(QLatin1StringView(request.uri(false)));
105
106
0
    return url;
107
0
}
108
109
} // Unnamed namespace
110
111
// Since we anyway end up having this in every function definition:
112
using namespace Http2;
113
114
QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *channel)
115
0
    : QAbstractProtocolHandler(channel)
116
0
{
117
0
    const auto h2Config = m_connection->http2Parameters();
118
119
0
    if (!channel->ssl
120
0
        && m_connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
121
0
        h2Connection = QHttp2Connection::createUpgradedConnection(channel->socket, h2Config);
122
        // Since we upgraded there is already one stream (the request was sent as http1)
123
        // and we need to handle it:
124
0
        QHttp2Stream *stream = h2Connection->getStream(1);
125
0
        Q_ASSERT(stream);
126
0
        Q_ASSERT(channel->reply);
127
0
        connectStream({ channel->request, channel->reply }, stream);
128
0
    } else {
129
0
        Q_ASSERT(QSocketAbstraction::socketState(channel->socket) == QAbstractSocket::ConnectedState);
130
0
        h2Connection = QHttp2Connection::createDirectConnection(channel->socket, h2Config);
131
0
    }
132
0
    connect(h2Connection, &QHttp2Connection::receivedGOAWAY, this,
133
0
            &QHttp2ProtocolHandler::handleGOAWAY);
134
0
    connect(h2Connection, &QHttp2Connection::errorOccurred, this,
135
0
            &QHttp2ProtocolHandler::connectionError);
136
0
    connect(h2Connection, &QHttp2Connection::newIncomingStream, this,
137
0
            [this](QHttp2Stream *stream){
138
                // Having our peer start streams doesn't make sense. We are
139
                // doing regular http request-response.
140
0
                stream->sendRST_STREAM(REFUSE_STREAM);
141
0
                if (!h2Connection->isGoingAway())
142
0
                    h2Connection->close(Http2::PROTOCOL_ERROR);
143
0
            });
144
0
    connect(h2Connection, &QHttp2Connection::connectionClosed, this,
145
0
            &QHttp2ProtocolHandler::closeSession);
146
0
}
147
148
bool QHttp2ProtocolHandler::isGoingAway() const noexcept
149
0
{
150
0
    return h2Connection && h2Connection->isGoingAway();
151
0
}
152
153
void QHttp2ProtocolHandler::handleConnectionClosure()
154
0
{
155
    // The channel has just received RemoteHostClosedError and since it will
156
    // not try (for HTTP/2) to re-connect, it's time to finish all replies
157
    // with error.
158
159
    // Maybe we still have some data to read and can successfully finish
160
    // a stream/request?
161
0
    _q_receiveReply();
162
0
    h2Connection->handleConnectionClosure();
163
0
}
164
165
void QHttp2ProtocolHandler::_q_uploadDataDestroyed(QObject *uploadData)
166
0
{
167
0
    QPointer<QHttp2Stream> stream = streamIDs.take(uploadData);
168
0
    if (stream && stream->isActive())
169
0
        stream->sendRST_STREAM(CANCEL);
170
0
}
171
172
void QHttp2ProtocolHandler::_q_readyRead()
173
0
{
174
0
    _q_receiveReply();
175
0
}
176
177
void QHttp2ProtocolHandler::_q_receiveReply()
178
0
{
179
    // not using QObject::connect because the QHttpNetworkConnectionChannel
180
    // already handles the signals we care about, so we just call the slot
181
    // directly.
182
0
    Q_ASSERT(h2Connection);
183
0
    h2Connection->handleReadyRead();
184
0
}
185
186
bool QHttp2ProtocolHandler::sendRequest()
187
0
{
188
0
    if (isGoingAway())
189
0
        return false;
190
191
    // Process 'fake' (created by QNetworkAccessManager::connectToHostEncrypted())
192
    // requests first:
193
0
    auto &requests = m_channel->h2RequestsToSend;
194
0
    for (auto it = requests.begin(), endIt = requests.end(); it != endIt;) {
195
0
        const auto &pair = *it;
196
0
        if (pair.first.isPreConnect()) {
197
0
            m_connection->preConnectFinished();
198
0
            emit pair.second->finished();
199
0
            it = requests.erase(it);
200
0
            if (requests.empty()) {
201
                // Normally, after a connection was established and H2
202
                // was negotiated, we send a client preface. connectToHostEncrypted
203
                // though is not meant to send any data, it's just a 'preconnect'.
204
                // Thus we return early:
205
0
                return true;
206
0
            }
207
0
        } else {
208
0
            ++it;
209
0
        }
210
0
    }
211
212
0
    if (requests.empty())
213
0
        return true;
214
215
0
    m_channel->state = QHttpNetworkConnectionChannel::WritingState;
216
    // Check what was promised/pushed, maybe we do not have to send a request
217
    // and have a response already?
218
219
0
    for (auto it = requests.begin(), end = requests.end(); it != end;) {
220
0
        HttpMessagePair &httpPair = *it;
221
222
0
        QUrl promiseKey = urlkey_from_request(httpPair.first);
223
0
        if (h2Connection->promisedStream(promiseKey) != nullptr) {
224
            // There's a PUSH_PROMISE for this request, so we don't send one
225
0
            initReplyFromPushPromise(httpPair, promiseKey);
226
0
            it = requests.erase(it);
227
0
            continue;
228
0
        }
229
230
0
        QHttp2Stream *stream = createNewStream(httpPair);
231
0
        if (!stream) { // There was an issue creating the stream
232
            // Check if it was unrecoverable, ie. the reply is errored out and finished:
233
0
            if (httpPair.second->isFinished()) {
234
0
                it = requests.erase(it);
235
0
            }
236
            // ... either way we stop looping:
237
0
            break;
238
0
        }
239
240
0
        QHttpNetworkRequest &request = requestReplyPairs[stream].first;
241
0
        if (!sendHEADERS(stream, request)) {
242
0
            finishStreamWithError(stream, QNetworkReply::UnknownNetworkError,
243
0
                    "failed to send HEADERS frame(s)"_L1);
244
0
            continue;
245
0
        }
246
0
        if (request.uploadByteDevice()) {
247
0
            if (!sendDATA(stream, httpPair.second)) {
248
0
                finishStreamWithError(stream, QNetworkReply::UnknownNetworkError,
249
0
                                      "failed to send DATA frame(s)"_L1);
250
0
                continue;
251
0
            }
252
0
        }
253
0
        it = requests.erase(it);
254
0
    }
255
256
0
    m_channel->state = QHttpNetworkConnectionChannel::IdleState;
257
258
0
    return true;
259
0
}
260
261
/*!
262
    \internal
263
    This gets called during destruction of \a reply, so do not call any functions
264
    on \a reply. We check if there is a stream associated with the reply and,
265
    if there is, we remove the request-reply pair associated with this stream,
266
    delete the stream and return \c{true}. Otherwise nothing happens and we
267
    return \c{false}.
268
*/
269
bool QHttp2ProtocolHandler::tryRemoveReply(QHttpNetworkReply *reply)
270
0
{
271
0
    QHttp2Stream *stream = streamIDs.take(reply);
272
0
    if (stream) {
273
0
        stream->sendRST_STREAM(stream->isUploadingDATA() ? Http2::CANCEL : Http2::HTTP2_NO_ERROR);
274
0
        clearStreamState(stream);
275
0
        stream->deleteLater();
276
0
        return true;
277
0
    }
278
0
    return false;
279
0
}
280
281
bool QHttp2ProtocolHandler::sendHEADERS(QHttp2Stream *stream, QHttpNetworkRequest &request)
282
0
{
283
0
    using namespace HPack;
284
285
0
    bool useProxy = false;
286
0
#ifndef QT_NO_NETWORKPROXY
287
0
    useProxy = m_connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy;
288
0
#endif
289
0
    if (request.withCredentials()) {
290
0
        m_connection->d_func()->createAuthorization(m_socket, request);
291
0
        request.d->needResendWithCredentials = false;
292
0
    }
293
0
    const auto headers = build_headers(request, h2Connection->maxHeaderListSize(), useProxy);
294
0
    if (headers.empty()) // nothing fits into maxHeaderListSize
295
0
        return false;
296
297
0
    bool mustUploadData = request.uploadByteDevice();
298
0
    return stream->sendHEADERS(headers, !mustUploadData);
299
0
}
300
301
bool QHttp2ProtocolHandler::sendDATA(QHttp2Stream *stream, QHttpNetworkReply *reply)
302
0
{
303
0
    Q_ASSERT(reply);
304
0
    QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
305
0
    Q_ASSERT(replyPrivate);
306
0
    QHttpNetworkRequest &request = replyPrivate->request;
307
0
    Q_ASSERT(request.uploadByteDevice());
308
309
0
    bool startedSending = stream->sendDATA(request.uploadByteDevice(), true);
310
0
    return startedSending && !stream->wasReset();
311
0
}
312
313
void QHttp2ProtocolHandler::handleHeadersReceived(const HPack::HttpHeader &headers, bool endStream)
314
0
{
315
0
    QHttp2Stream *stream = qobject_cast<QHttp2Stream *>(sender());
316
0
    Q_ASSERT(stream);
317
0
    auto &requestPair = requestReplyPairs[stream];
318
0
    auto *httpReply = requestPair.second;
319
0
    auto &httpRequest = requestPair.first;
320
0
    if (!httpReply)
321
0
        return;
322
323
0
    auto *httpReplyPrivate = httpReply->d_func();
324
325
    // For HTTP/1 'location' is handled (and redirect URL set) when a protocol
326
    // handler emits channel->allDone(). Http/2 protocol handler never emits
327
    // allDone, since we have many requests multiplexed in one channel at any
328
    // moment and we are probably not done yet. So we extract url and set it
329
    // here, if needed.
330
0
    int statusCode = 0;
331
0
    for (const auto &pair : headers) {
332
0
        const auto &name = pair.name;
333
0
        const auto value = QByteArrayView(pair.value);
334
335
        // TODO: part of this code copies what SPDY protocol handler does when
336
        // processing headers. Binary nature of HTTP/2 and SPDY saves us a lot
337
        // of parsing and related errors/bugs, but it would be nice to have
338
        // more detailed validation of headers.
339
0
        if (name == ":status") {
340
0
            bool ok = false;
341
0
            if (int status = value.toInt(&ok); ok && status >= 0 && status <= 999) {
342
0
                statusCode = status;
343
0
                httpReply->setStatusCode(statusCode);
344
0
                m_channel->lastStatus = statusCode; // Mostly useless for http/2, needed for auth
345
0
            } else {
346
0
                finishStreamWithError(stream, QNetworkReply::ProtocolInvalidOperationError,
347
0
                                      "invalid :status value"_L1);
348
0
                return;
349
0
            }
350
0
        } else if (name == "content-length") {
351
0
            bool ok = false;
352
0
            const qlonglong length = value.toLongLong(&ok);
353
0
            if (ok)
354
0
                httpReply->setContentLength(length);
355
0
        } else {
356
0
            const auto binder = name == "set-cookie" ? QByteArrayView("\n") : QByteArrayView(", ");
357
0
            httpReply->appendHeaderField(name, QByteArray(pair.value).replace('\0', binder));
358
0
        }
359
0
    }
360
361
    // Discard all informational (1xx) replies with the exception of 101.
362
    // Also see RFC 9110 (Chapter 15.2)
363
0
    if (statusCode == 100 || (102 <= statusCode && statusCode <= 199)) {
364
0
        httpReplyPrivate->clearHttpLayerInformation();
365
0
        return;
366
0
    }
367
368
0
    if (QHttpNetworkReply::isHttpRedirect(statusCode) && httpRequest.isFollowRedirects()) {
369
0
        QHttpNetworkConnectionPrivate::ParseRedirectResult
370
0
                result = QHttpNetworkConnectionPrivate::parseRedirectResponse(httpReply);
371
0
        if (result.errorCode != QNetworkReply::NoError) {
372
0
            auto errorString = m_connection->d_func()->errorDetail(result.errorCode, m_socket);
373
0
            finishStreamWithError(stream, result.errorCode, errorString);
374
0
            stream->sendRST_STREAM(INTERNAL_ERROR);
375
0
            return;
376
0
        }
377
378
0
        if (result.redirectUrl.isValid())
379
0
            httpReply->setRedirectUrl(result.redirectUrl);
380
0
    }
381
382
0
    if (httpReplyPrivate->isCompressed() && httpRequest.d->autoDecompress)
383
0
        httpReplyPrivate->removeAutoDecompressHeader();
384
385
0
    if (QHttpNetworkReply::isHttpRedirect(statusCode)) {
386
        // Note: This status code can trigger uploadByteDevice->reset() in
387
        // QHttpNetworkConnectionChannel::handleStatus. Alas, we have no single
388
        // request/reply, we multiplex several requests and thus we never simply
389
        // call 'handleStatus'. If we have a byte-device - we try to reset it
390
        // here, we don't (and can't) handle any error during reset operation.
391
0
        if (auto *byteDevice = httpRequest.uploadByteDevice()) {
392
0
            byteDevice->reset();
393
0
            httpReplyPrivate->totallyUploadedData = 0;
394
0
        }
395
0
    }
396
397
0
    QMetaObject::invokeMethod(httpReply, &QHttpNetworkReply::headerChanged, Qt::QueuedConnection);
398
0
    if (endStream)
399
0
        finishStream(stream, Qt::QueuedConnection);
400
0
}
401
402
void QHttp2ProtocolHandler::handleDataReceived(const QByteArray &data, bool endStream)
403
0
{
404
0
    QHttp2Stream *stream = qobject_cast<QHttp2Stream *>(sender());
405
0
    auto &httpPair = requestReplyPairs[stream];
406
0
    auto *httpReply = httpPair.second;
407
0
    if (!httpReply)
408
0
        return;
409
0
    Q_ASSERT(!stream->isPromisedStream());
410
411
0
    if (!data.isEmpty() && !httpPair.first.d->needResendWithCredentials) {
412
0
        auto *replyPrivate = httpReply->d_func();
413
414
0
        replyPrivate->totalProgress += data.size();
415
416
0
        replyPrivate->responseData.append(data);
417
418
0
        if (replyPrivate->shouldEmitSignals()) {
419
0
            QMetaObject::invokeMethod(httpReply, &QHttpNetworkReply::readyRead,
420
0
                                      Qt::QueuedConnection);
421
0
            QMetaObject::invokeMethod(httpReply, &QHttpNetworkReply::dataReadProgress,
422
0
                                      Qt::QueuedConnection, replyPrivate->totalProgress,
423
0
                                      replyPrivate->bodyLength);
424
0
        }
425
0
    }
426
0
    stream->clearDownloadBuffer();
427
0
    if (endStream)
428
0
        finishStream(stream, Qt::QueuedConnection);
429
0
}
430
431
// After calling this function, either the request will be re-sent or
432
// the reply will be finishedWithError! Do not emit finished() or similar on the
433
// reply after this!
434
void QHttp2ProtocolHandler::handleAuthorization(QHttp2Stream *stream)
435
0
{
436
0
    auto &requestPair = requestReplyPairs[stream];
437
0
    auto *httpReply = requestPair.second;
438
0
    auto *httpReplyPrivate = httpReply->d_func();
439
0
    auto &httpRequest = requestPair.first;
440
441
0
    Q_ASSERT(httpReply && (httpReply->statusCode() == 401 || httpReply->statusCode() == 407));
442
443
0
    const auto handleAuth = [&, this](QByteArrayView authField, bool isProxy) -> bool {
444
0
        Q_ASSERT(httpReply);
445
0
        const QByteArrayView auth = authField.trimmed();
446
0
        if (auth.startsWith("Negotiate") || auth.startsWith("NTLM")) {
447
            // NTLM/Kerberos/Negotiate authentication is not supported with HTTP/2.
448
            // Finish the stream with an error so QNetworkReply::finished is emitted.
449
            // Falling back to HTTP/1.1 is a separate, larger effort (QTBUG-143926).
450
0
            emit httpReply->headerChanged();
451
0
            emit httpReply->readyRead();
452
0
            const QNetworkReply::NetworkError error = isProxy
453
0
                    ? QNetworkReply::ProxyAuthenticationRequiredError
454
0
                    : QNetworkReply::AuthenticationRequiredError;
455
0
            finishStreamWithError(stream, error,
456
0
                                  m_connection->d_func()->errorDetail(error, m_socket));
457
0
            return false;
458
0
        }
459
        // Somewhat mimics parts of QHttpNetworkConnectionChannel::handleStatus
460
0
        bool resend = false;
461
0
        const bool authenticateHandled = m_connection->d_func()->handleAuthenticateChallenge(
462
0
                m_socket, httpReply, isProxy, resend);
463
0
        if (authenticateHandled) {
464
0
            if (resend) {
465
0
                httpReply->d_func()->eraseData();
466
                // Add the request back in queue, we'll retry later now that
467
                // we've gotten some username/password set on it:
468
0
                httpRequest.d->needResendWithCredentials = true;
469
0
                m_channel->h2RequestsToSend.insert(httpRequest.priority(), requestPair);
470
0
                httpReply->d_func()->clearHeaders();
471
                // If we have data we were uploading we need to reset it:
472
0
                if (auto *byteDevice = httpRequest.uploadByteDevice()) {
473
0
                    byteDevice->reset();
474
0
                    httpReplyPrivate->totallyUploadedData = 0;
475
0
                }
476
                // We automatically try to send new requests when the stream is
477
                // closed, so we don't need to call sendRequest ourselves.
478
0
                return true;
479
0
            } // else: we're just not resending the request.
480
            // @note In the http/1.x case we (at time of writing) call close()
481
            // for the connectionChannel (which is a bit weird, we could surely
482
            // reuse the open socket outside "connection:close"?), but in http2
483
            // we only have one channel, so we won't close anything.
484
0
        } else {
485
            // No authentication header or authentication isn't supported, but
486
            // we got a 401/407 so we cannot succeed. We need to emit signals
487
            // for headers and data, and then finishWithError.
488
0
            emit httpReply->headerChanged();
489
0
            emit httpReply->readyRead();
490
0
            QNetworkReply::NetworkError error = httpReply->statusCode() == 401
491
0
                    ? QNetworkReply::AuthenticationRequiredError
492
0
                    : QNetworkReply::ProxyAuthenticationRequiredError;
493
0
            finishStreamWithError(stream, QNetworkReply::AuthenticationRequiredError,
494
0
                                  m_connection->d_func()->errorDetail(error, m_socket));
495
0
        }
496
0
        return false;
497
0
    };
498
499
    // These statuses would in HTTP/1.1 be handled by
500
    // QHttpNetworkConnectionChannel::handleStatus. But because h2 has
501
    // multiple streams/requests in a single channel this structure does not
502
    // map properly to that function.
503
0
    bool authOk = true;
504
0
    switch (httpReply->statusCode()) {
505
0
    case 401:
506
0
        authOk = handleAuth(httpReply->headerField("www-authenticate"), false);
507
0
        break;
508
0
    case 407:
509
0
        authOk = handleAuth(httpReply->headerField("proxy-authenticate"), true);
510
0
        break;
511
0
    default:
512
0
        Q_UNREACHABLE();
513
0
    }
514
0
    if (authOk) {
515
0
        stream->sendRST_STREAM(CANCEL);
516
0
        clearStreamState(stream);
517
0
        stream->deleteLater();
518
0
    } // else: errors handled inside handleAuth
519
0
}
520
521
// Called when we have received a frame with the END_STREAM flag set
522
void QHttp2ProtocolHandler::finishStream(QHttp2Stream *stream, Qt::ConnectionType connectionType)
523
0
{
524
0
    if (stream->state() != QHttp2Stream::State::Closed)
525
0
        stream->sendRST_STREAM(CANCEL);
526
527
0
    auto &pair = requestReplyPairs[stream];
528
0
    auto *httpReply = pair.second;
529
0
    if (httpReply) {
530
0
        int statusCode = httpReply->statusCode();
531
0
        if (statusCode == 401 || statusCode == 407) {
532
            // handleAuthorization will either re-send the request or
533
            // finishWithError. In either case we don't want to emit finished
534
            // here.
535
0
            handleAuthorization(stream);
536
0
            return;
537
0
        }
538
539
0
        httpReply->disconnect(this);
540
541
0
        if (!pair.first.d->needResendWithCredentials) {
542
0
            if (connectionType == Qt::DirectConnection)
543
0
                emit httpReply->finished();
544
0
            else
545
0
                QMetaObject::invokeMethod(httpReply, &QHttpNetworkReply::finished, connectionType);
546
0
        }
547
0
    }
548
549
0
    clearStreamState(stream);
550
0
    qCDebug(QT_HTTP2) << "stream" << stream->streamID() << "closed";
551
552
    // Detach the reply and tear down the stream via canonical path, so a
553
    // later stray frame cannot fail an already-finished reply. tryRemoveReply()
554
    // also clears the reply->stream map, its sendRST_STREAM() does nothing on a
555
    // gracefully closed stream.
556
0
    if (!httpReply || !tryRemoveReply(httpReply)) {
557
0
        requestReplyPairs.remove(stream);
558
0
        stream->deleteLater();
559
0
    }
560
0
}
561
562
void QHttp2ProtocolHandler::handleGOAWAY(Http2Error errorCode, quint32 lastStreamID)
563
0
{
564
0
    qCDebug(QT_HTTP2) << "GOAWAY received, error code:" << errorCode << "last stream ID:"
565
0
                      << lastStreamID;
566
567
0
    if (errorCode == HTTP2_NO_ERROR) {
568
        // Graceful GOAWAY: the queued requests are sent on the next connection.
569
        // *noop*
570
0
    } else {
571
        // Error GOAWAY: the queued requests were never processed, so a retry could still
572
        // succeed, but something went wrong on this connection - report it to the user
573
        // rather than silently trying again.
574
0
        m_channel->emitFinishedWithError(QNetworkReply::ProtocolUnknownError,
575
0
                                         "GOAWAY received, cannot start a request");
576
0
        m_channel->h2RequestsToSend.clear();
577
0
    }
578
579
    // Active streams are not handled here: QHttp2Connection::handleGOAWAY() cancels the ones
580
    // the peer will not process and we pick them up through QHttp2Stream::errorOccurred.
581
0
}
582
583
void QHttp2ProtocolHandler::finishStreamWithError(QHttp2Stream *stream, Http2Error errorCode)
584
0
{
585
0
    QNetworkReply::NetworkError error = QNetworkReply::NoError;
586
0
    QString message;
587
0
    qt_error(errorCode, error, message);
588
0
    finishStreamWithError(stream, error, message);
589
0
}
590
591
void QHttp2ProtocolHandler::finishStreamWithError(QHttp2Stream *stream,
592
        QNetworkReply::NetworkError error, const QString &message)
593
0
{
594
0
    stream->sendRST_STREAM(CANCEL);
595
0
    const HttpMessagePair &pair = requestReplyPairs.value(stream);
596
0
    if (auto *httpReply = pair.second) {
597
0
        httpReply->disconnect(this);
598
599
        // TODO: error message must be translated!!! (tr)
600
0
        emit httpReply->finishedWithError(error, message);
601
0
    }
602
603
0
    clearStreamState(stream);
604
0
    stream->deleteLater();
605
0
    qCWarning(QT_HTTP2) << "stream" << stream->streamID() << "finished with error:" << message;
606
0
}
607
608
/*!
609
    \internal
610
611
    Creates a QHttp2Stream for the request, will return \nullptr if the stream
612
    could not be created for some reason, and will finish the reply if required.
613
*/
614
QHttp2Stream *QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message,
615
                                                     bool uploadDone)
616
0
{
617
0
    QUrl streamKey = urlkey_from_request(message.first);
618
0
    if (auto promisedStream = h2Connection->promisedStream(streamKey)) {
619
0
        Q_ASSERT(promisedStream->state() != QHttp2Stream::State::Closed);
620
0
        return promisedStream;
621
0
    }
622
623
0
    QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
624
0
            streamResult = h2Connection->createStream();
625
0
    if (!streamResult.ok()) {
626
0
        if (streamResult.error()
627
0
            == QHttp2Connection::CreateStreamError::MaxConcurrentStreamsReached) {
628
            // We have to wait for a stream to be closed before we can create a new one, so
629
            // we just return nullptr, the caller should not remove it from the queue.
630
0
            return nullptr;
631
0
        }
632
0
        qCDebug(QT_HTTP2) << "failed to create new stream:" << streamResult.error();
633
0
        auto *reply = message.second;
634
0
        const char *cstr = "Failed to initialize HTTP/2 stream with errorcode: %1";
635
0
        const QString errorString = QCoreApplication::translate("QHttp", cstr)
636
0
                                            .arg(QDebug::toString(streamResult.error()));
637
0
        emit reply->finishedWithError(QNetworkReply::ProtocolFailure, errorString);
638
0
        return nullptr;
639
0
    }
640
0
    QHttp2Stream *stream = streamResult.unwrap();
641
642
0
    if (!uploadDone) {
643
0
        if (auto *src = message.first.uploadByteDevice()) {
644
0
            connect(src, &QObject::destroyed, this, &QHttp2ProtocolHandler::_q_uploadDataDestroyed);
645
0
            streamIDs.insert(src, stream);
646
0
        }
647
0
    }
648
649
0
    auto *reply = message.second;
650
0
    QMetaObject::invokeMethod(reply, &QHttpNetworkReply::requestSent, Qt::QueuedConnection);
651
652
0
    connectStream(message, stream);
653
0
    return stream;
654
0
}
655
656
void QHttp2ProtocolHandler::connectStream(const HttpMessagePair &message, QHttp2Stream *stream)
657
0
{
658
0
    auto *reply = message.second;
659
0
    auto *replyPrivate = reply->d_func();
660
0
    replyPrivate->connection = m_connection;
661
0
    replyPrivate->connectionChannel = m_channel;
662
663
0
    reply->setHttp2WasUsed(true);
664
0
    QPointer<QHttp2Stream> &oldStream = streamIDs[reply];
665
0
    if (oldStream) {
666
0
        disconnect(oldStream, nullptr, this, nullptr);
667
0
        clearStreamState(oldStream);
668
0
    }
669
0
    oldStream = stream;
670
0
    requestReplyPairs.emplace(stream, message);
671
672
0
    QObject::connect(stream, &QHttp2Stream::headersReceived, this,
673
0
                     &QHttp2ProtocolHandler::handleHeadersReceived);
674
0
    QObject::connect(stream, &QHttp2Stream::dataReceived, this,
675
0
                     &QHttp2ProtocolHandler::handleDataReceived);
676
0
    QObject::connect(stream, &QHttp2Stream::errorOccurred, this,
677
0
                     [this, stream](Http2Error errorCode, const QString &errorString) {
678
0
                         qCWarning(QT_HTTP2)
679
0
                                 << "stream" << stream->streamID() << "error:" << errorString;
680
                         // A graceful GOAWAY cancels exactly the streams the peer will not
681
                         // process, so those requests can still be sent on a new connection.
682
                         // An error GOAWAY, or any other failure, keeps its own error.
683
0
                         if (const auto lastId = h2Connection->lastGoAwayStreamID();
684
0
                             errorCode == HTTP2_NO_ERROR && lastId
685
0
                             && stream->streamID() > *lastId) {
686
0
                             finishStreamWithError(
687
0
                                     stream, QNetworkReply::ContentReSendError,
688
0
                                     QCoreApplication::translate(
689
0
                                             "QHttp",
690
0
                                             "Server stopped accepting new streams before this "
691
0
                                             "stream was established"));
692
0
                         } else {
693
0
                             finishStreamWithError(stream, errorCode);
694
0
                         }
695
0
                     });
696
697
0
    QObject::connect(stream, &QHttp2Stream::stateChanged, this, [this](QHttp2Stream::State state) {
698
0
        if (state == QHttp2Stream::State::Closed) {
699
            // Try to send more requests if we have any
700
0
            if (!m_channel->h2RequestsToSend.empty()) {
701
0
                QMetaObject::invokeMethod(this, &QHttp2ProtocolHandler::sendRequest,
702
0
                                          Qt::QueuedConnection);
703
0
            }
704
0
        }
705
0
    });
706
0
}
707
708
void QHttp2ProtocolHandler::clearStreamState(QHttp2Stream *stream)
709
0
{
710
0
    auto it = requestReplyPairs.find(stream);
711
0
    if (it == requestReplyPairs.end())
712
0
        return;
713
714
0
    if (auto *reply = it->second)
715
0
        streamIDs.remove(reply);
716
0
    if (auto *uploadDevice = it->first.uploadByteDevice())
717
0
        streamIDs.remove(uploadDevice);
718
719
0
    requestReplyPairs.erase(it);
720
0
}
721
722
void QHttp2ProtocolHandler::initReplyFromPushPromise(const HttpMessagePair &message,
723
                                                     const QUrl &cacheKey)
724
0
{
725
0
    QHttp2Stream *promise = h2Connection->promisedStream(cacheKey);
726
0
    Q_ASSERT(promise);
727
0
    Q_ASSERT(message.second);
728
0
    message.second->setHttp2WasUsed(true);
729
730
0
    qCDebug(QT_HTTP2) << "found cached/promised response on stream" << promise->streamID();
731
732
0
    const bool replyFinished = promise->state() == QHttp2Stream::State::Closed;
733
734
0
    connectStream(message, promise);
735
736
    // Now that we have connect()ed, re-emit signals so that the reply
737
    // can be processed as usual:
738
739
0
    QByteDataBuffer downloadBuffer = promise->takeDownloadBuffer();
740
0
    if (const auto &headers = promise->receivedHeaders(); !headers.empty())
741
0
        emit promise->headersReceived(headers, replyFinished && downloadBuffer.isEmpty());
742
743
0
    if (!downloadBuffer.isEmpty()) {
744
0
        for (qsizetype i = 0; i < downloadBuffer.bufferCount(); ++i) {
745
0
            const bool streamEnded = replyFinished && i == downloadBuffer.bufferCount() - 1;
746
0
            emit promise->dataReceived(downloadBuffer[i], streamEnded);
747
0
        }
748
0
    }
749
0
}
750
751
void QHttp2ProtocolHandler::connectionError(Http2::Http2Error errorCode, const QString &message)
752
0
{
753
0
    Q_ASSERT(!message.isNull());
754
755
0
    qCCritical(QT_HTTP2) << "connection error:" << message;
756
757
0
    const auto error = qt_error(errorCode);
758
0
    m_channel->emitFinishedWithError(error, message);
759
0
    m_channel->h2RequestsToSend.clear();
760
761
0
    closeSession();
762
0
}
763
764
void QHttp2ProtocolHandler::closeSession()
765
0
{
766
0
    m_channel->close();
767
0
}
768
769
QT_END_NAMESPACE
770
771
#include "moc_qhttp2protocolhandler_p.cpp"