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/qhttpnetworkconnection.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 "qhttpnetworkconnection_p.h"
6
#include <private/qabstractsocket_p.h>
7
#include "qhttpnetworkconnectionchannel_p.h"
8
#include <private/qhttp2protocolhandler_p.h>
9
#include "private/qnoncontiguousbytedevice_p.h"
10
#include <private/qnetworkrequest_p.h>
11
#include <private/qobject_p.h>
12
#include <private/qauthenticator_p.h>
13
#include "private/qhostinfo_p.h"
14
#include <qnetworkproxy.h>
15
#include <qauthenticator.h>
16
#include <qcoreapplication.h>
17
#include <private/qdecompresshelper_p.h>
18
#include <private/qsocketabstraction_p.h>
19
20
#include <qbuffer.h>
21
#include <qdebug.h>
22
#include <qspan.h>
23
#include <qvarlengtharray.h>
24
25
#ifndef QT_NO_SSL
26
#    include <private/qsslsocket_p.h>
27
#    include <QtNetwork/qsslkey.h>
28
#    include <QtNetwork/qsslcipher.h>
29
#    include <QtNetwork/qsslconfiguration.h>
30
#    include <QtNetwork/qsslerror.h>
31
#endif
32
33
34
35
QT_BEGIN_NAMESPACE
36
37
using namespace Qt::StringLiterals;
38
39
// The pipeline length. So there will be 4 requests in flight.
40
const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3;
41
// Only re-fill the pipeline if there's defaultRePipelineLength slots free in the pipeline.
42
// This means that there are 2 requests in flight and 2 slots free that will be re-filled.
43
const int QHttpNetworkConnectionPrivate::defaultRePipelineLength = 2;
44
45
static int getPreferredActiveChannelCount(QHttpNetworkConnection::ConnectionType type,
46
                                          int defaultValue)
47
0
{
48
0
    return (type == QHttpNetworkConnection::ConnectionTypeHTTP2
49
0
            || type == QHttpNetworkConnection::ConnectionTypeHTTP2Direct)
50
0
            ? 1
51
0
            : defaultValue;
52
0
}
53
54
QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(
55
        quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt,
56
        bool isLocalSocket, QHttpNetworkConnection::ConnectionType type)
57
0
    : hostName(hostName),
58
0
      port(port),
59
0
      encrypt(encrypt),
60
0
      isLocalSocket(isLocalSocket),
61
0
      activeChannelCount(getPreferredActiveChannelCount(type, connectionCount)),
62
0
      channelCount(connectionCount),
63
0
      channels(new QHttpNetworkConnectionChannel[channelCount]),
64
#ifndef QT_NO_NETWORKPROXY
65
0
      networkProxy(QNetworkProxy::NoProxy),
66
#endif
67
0
      connectionType(type)
68
0
{
69
0
    if (isLocalSocket) // Don't try to do host lookup for local sockets
70
0
        networkLayerState = IPv4;
71
    // We allocate all 6 channels even if it's an HTTP/2-enabled
72
    // connection: in case the protocol negotiation via NPN/ALPN fails,
73
    // we will have normally working HTTP/1.1.
74
0
    Q_ASSERT(channelCount >= activeChannelCount);
75
0
}
76
77
78
79
QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate()
80
0
{
81
0
    for (int i = 0; i < channelCount; ++i) {
82
0
        if (channels[i].socket) {
83
0
            QObject::disconnect(channels[i].socket, nullptr, &channels[i], nullptr);
84
0
            channels[i].socket->close();
85
0
            delete channels[i].socket;
86
0
        }
87
0
    }
88
0
    delete []channels;
89
0
}
90
91
void QHttpNetworkConnectionPrivate::init()
92
0
{
93
0
    Q_Q(QHttpNetworkConnection);
94
0
    for (int i = 0; i < channelCount; i++) {
95
0
        channels[i].setConnection(this->q_func());
96
0
        channels[i].ssl = encrypt;
97
0
    }
98
99
0
    delayedConnectionTimer.setSingleShot(true);
100
0
    QObject::connect(&delayedConnectionTimer, SIGNAL(timeout()), q, SLOT(_q_connectDelayedChannel()));
101
0
}
102
103
void QHttpNetworkConnectionPrivate::pauseConnection()
104
0
{
105
0
    state = PausedState;
106
107
    // Disable all socket notifiers
108
0
    for (int i = 0; i < activeChannelCount; i++) {
109
0
        if (auto *absSocket = qobject_cast<QAbstractSocket *>(channels[i].socket)) {
110
0
#ifndef QT_NO_SSL
111
0
            if (encrypt)
112
0
                QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(absSocket));
113
0
            else
114
0
#endif
115
0
                QAbstractSocketPrivate::pauseSocketNotifiers(absSocket);
116
0
#if QT_CONFIG(localserver)
117
0
        } else if (qobject_cast<QLocalSocket *>(channels[i].socket)) {
118
            // @todo how would we do this?
119
#if 0 // @todo Enable this when there is a debug category for this
120
            qDebug() << "Should pause socket but there is no way to do it for local sockets";
121
#endif
122
0
#endif
123
0
        }
124
0
    }
125
0
}
126
127
void QHttpNetworkConnectionPrivate::resumeConnection()
128
0
{
129
0
    state = RunningState;
130
    // Enable all socket notifiers
131
0
    for (int i = 0; i < activeChannelCount; i++) {
132
0
        if (auto *absSocket = qobject_cast<QAbstractSocket *>(channels[i].socket)) {
133
0
#ifndef QT_NO_SSL
134
0
            if (encrypt)
135
0
                QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(absSocket));
136
0
            else
137
0
#endif
138
0
                QAbstractSocketPrivate::resumeSocketNotifiers(absSocket);
139
140
            // Resume pending upload if needed
141
0
            if (channels[i].state == QHttpNetworkConnectionChannel::WritingState)
142
0
                QMetaObject::invokeMethod(&channels[i], "_q_uploadDataReadyRead", Qt::QueuedConnection);
143
0
#if QT_CONFIG(localserver)
144
0
        } else if (qobject_cast<QLocalSocket *>(channels[i].socket)) {
145
#if 0 // @todo Enable this when there is a debug category for this
146
            qDebug() << "Should resume socket but there is no way to do it for local sockets";
147
#endif
148
0
#endif
149
0
        }
150
0
    }
151
152
    // queue _q_startNextRequest
153
0
    QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
154
0
}
155
156
int QHttpNetworkConnectionPrivate::indexOf(QIODevice *socket) const
157
0
{
158
0
    for (int i = 0; i < activeChannelCount; ++i)
159
0
        if (channels[i].socket == socket)
160
0
            return i;
161
162
0
    qFatal("Called with unknown socket object.");
163
0
    return 0;
164
0
}
165
166
// If the connection is in the HostLookupPendening state channel errors should not always be
167
// emitted. This function will check the status of the connection channels if we
168
// have not decided the networkLayerState and will return true if the channel error
169
// should be emitted by the channel.
170
bool QHttpNetworkConnectionPrivate::shouldEmitChannelError(QIODevice *socket)
171
0
{
172
0
    Q_Q(QHttpNetworkConnection);
173
174
0
    bool emitError = true;
175
0
    int i = indexOf(socket);
176
0
    int otherSocket = (i == 0 ? 1 : 0);
177
178
    // If the IPv4 connection still isn't started we need to start it now.
179
0
    if (delayedConnectionTimer.isActive()) {
180
0
        delayedConnectionTimer.stop();
181
0
        channels[otherSocket].ensureConnection();
182
0
    }
183
184
0
    if (activeChannelCount < channelCount) {
185
0
        if (networkLayerState == HostLookupPending || networkLayerState == IPv4or6)
186
0
            networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
187
0
        channels[0].close();
188
0
        emitError = true;
189
0
    } else {
190
0
        if (networkLayerState == HostLookupPending || networkLayerState == IPv4or6) {
191
0
            if (channels[otherSocket].isSocketBusy() && (channels[otherSocket].state != QHttpNetworkConnectionChannel::ClosingState)) {
192
                // this was the first socket to fail.
193
0
                channels[i].close();
194
0
                emitError = false;
195
0
            }
196
0
            else {
197
                // Both connection attempts has failed.
198
0
                networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
199
0
                channels[i].close();
200
0
                emitError = true;
201
0
            }
202
0
        } else {
203
0
            if (((networkLayerState == QHttpNetworkConnectionPrivate::IPv4) && (channels[i].networkLayerPreference != QAbstractSocket::IPv4Protocol))
204
0
                || ((networkLayerState == QHttpNetworkConnectionPrivate::IPv6) && (channels[i].networkLayerPreference != QAbstractSocket::IPv6Protocol))) {
205
                // First connection worked so this is the second one to complete and it failed.
206
0
                channels[i].close();
207
0
                QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
208
0
                emitError = false;
209
0
            }
210
0
            if (networkLayerState == QHttpNetworkConnectionPrivate::Unknown)
211
0
                qWarning("We got a connection error when networkLayerState is Unknown");
212
0
        }
213
0
    }
214
0
    return emitError;
215
0
}
216
217
218
qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailable(const QHttpNetworkReply &reply) const
219
0
{
220
0
    return reply.d_func()->responseData.byteAmount();
221
0
}
222
223
qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailableNextBlock(const QHttpNetworkReply &reply) const
224
0
{
225
0
    return reply.d_func()->responseData.sizeNextBlock();
226
0
}
227
228
static QByteArray makeAcceptLanguage()
229
0
{
230
0
    QString systemLocale = QLocale::system().name();
231
0
    if (systemLocale == "C"_L1)
232
0
        return "en,*"_ba;
233
0
    systemLocale.replace('_'_L1, '-'_L1);
234
0
    if (systemLocale.startsWith("en-"_L1))
235
0
        return (systemLocale + ",*"_L1).toLatin1();
236
0
    return (systemLocale + ",en,*"_L1).toLatin1();
237
0
}
238
239
static QStringView removeZoneId(QStringView ipv6HostAddress)
240
0
{
241
0
    const auto zoneIdentfierIndex = ipv6HostAddress.indexOf(u'%');
242
    // Only perform a minimal sanity check, as at this point the
243
    // ipv6HostAddress was already used successfully to establish the connection.
244
0
    if (zoneIdentfierIndex == -1) {
245
0
        return ipv6HostAddress;
246
0
    }
247
248
0
    return ipv6HostAddress.left(zoneIdentfierIndex);
249
0
}
250
251
void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
252
0
{
253
0
    QHttpNetworkRequest &request = messagePair.first;
254
0
    QHttpNetworkReply *reply = messagePair.second;
255
256
    // add missing fields for the request
257
0
    QByteArray value;
258
0
#ifndef Q_OS_WASM
259
    // check if Content-Length is provided
260
0
    QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
261
0
    if (uploadByteDevice) {
262
0
        const qint64 contentLength = request.contentLength();
263
0
        const qint64 uploadDeviceSize = uploadByteDevice->size();
264
0
        if (contentLength != -1 && uploadDeviceSize != -1) {
265
            // Both values known: use the smaller one.
266
0
            if (uploadDeviceSize < contentLength)
267
0
                request.setContentLength(uploadDeviceSize);
268
0
        } else if (contentLength == -1 && uploadDeviceSize != -1) {
269
            // content length not supplied by user, but the upload device knows it
270
0
            request.setContentLength(uploadDeviceSize);
271
0
        } else if (contentLength != -1 && uploadDeviceSize == -1) {
272
            // everything OK, the user supplied us the contentLength
273
0
        } else if (Q_UNLIKELY(contentLength == -1 && uploadDeviceSize == -1)) {
274
0
            qFatal("QHttpNetworkConnectionPrivate: Neither content-length nor upload device size were given");
275
0
        }
276
0
    }
277
0
#endif
278
    // set the Connection/Proxy-Connection: Keep-Alive headers
279
0
#ifndef QT_NO_NETWORKPROXY
280
0
    if (networkProxy.type() == QNetworkProxy::HttpCachingProxy)  {
281
0
        value = request.headerField("proxy-connection");
282
0
        if (value.isEmpty())
283
0
            request.setHeaderField("Proxy-Connection", "Keep-Alive");
284
0
    } else {
285
0
#endif
286
0
        value = request.headerField("connection");
287
0
        if (value.isEmpty())
288
0
            request.setHeaderField("Connection", "Keep-Alive");
289
0
#ifndef QT_NO_NETWORKPROXY
290
0
    }
291
0
#endif
292
293
    // If the request had a accept-encoding set, we better not mess
294
    // with it. If it was not set, we announce that we understand gzip
295
    // and remember this fact in request.d->autoDecompress so that
296
    // we can later decompress the HTTP reply if it has such an
297
    // encoding.
298
0
    value = request.headerField("accept-encoding");
299
0
    if (value.isEmpty()) {
300
0
#ifndef QT_NO_COMPRESS
301
0
        const static QByteArray acceptedEncoding = QDecompressHelper::acceptedEncoding().join(", ");
302
0
        request.setHeaderField("Accept-Encoding", acceptedEncoding);
303
0
        request.d->autoDecompress = true;
304
#else
305
        // if zlib is not available set this to false always
306
        request.d->autoDecompress = false;
307
#endif
308
0
    }
309
310
    // some websites mandate an accept-language header and fail
311
    // if it is not sent. This is a problem with the website and
312
    // not with us, but we work around this by setting
313
    // one always.
314
0
    value = request.headerField("accept-language");
315
0
    if (value.isEmpty())
316
0
        request.setHeaderField("Accept-Language", makeAcceptLanguage());
317
318
    // set the User Agent
319
0
    value = request.headerField("user-agent");
320
0
    if (value.isEmpty())
321
0
        request.setHeaderField("User-Agent", "Mozilla/5.0");
322
    // set the host
323
0
    value = request.headerField("host");
324
0
    if (isLocalSocket && value.isEmpty()) {
325
        // The local socket connections might have a full file path, and that
326
        // may not be suitable for the Host header. But we can use whatever the
327
        // user has set in the URL.
328
0
        request.prependHeaderField("Host", request.url().host().toLocal8Bit());
329
0
    } else if (value.isEmpty()) {
330
0
        QHostAddress add;
331
0
        QByteArray host;
332
0
        if (add.setAddress(hostName)) {
333
0
            if (add.protocol() == QAbstractSocket::IPv6Protocol)
334
0
                host = (u'[' + removeZoneId(hostName) + u']').toLatin1(); //format the ipv6 in the standard way
335
0
            else
336
0
                host = hostName.toLatin1();
337
338
0
        } else {
339
0
            host = QUrl::toAce(hostName);
340
0
        }
341
342
0
        int port = request.url().port();
343
0
        if (port != -1) {
344
0
            host += ':';
345
0
            host += QByteArray::number(port);
346
0
        }
347
348
0
        request.prependHeaderField("Host", host);
349
0
    }
350
351
0
    reply->d_func()->requestIsPrepared = true;
352
0
}
353
354
355
356
357
void QHttpNetworkConnectionPrivate::emitReplyError(QIODevice *socket,
358
                                                   QHttpNetworkReply *reply,
359
                                                   QNetworkReply::NetworkError errorCode)
360
0
{
361
0
    Q_Q(QHttpNetworkConnection);
362
363
0
    int i = 0;
364
0
    if (socket)
365
0
        i = indexOf(socket);
366
367
0
    if (reply) {
368
        // this error matters only to this reply
369
0
        reply->d_func()->errorString = errorDetail(errorCode, socket);
370
0
        emit reply->finishedWithError(errorCode, reply->d_func()->errorString);
371
        // remove the corrupt data if any
372
0
        reply->d_func()->eraseData();
373
374
        // Clean the channel
375
0
        channels[i].close();
376
0
        channels[i].reply = nullptr;
377
0
        if (channels[i].protocolHandler)
378
0
            channels[i].protocolHandler->setReply(nullptr);
379
0
        channels[i].request = QHttpNetworkRequest();
380
0
        if (socket)
381
0
            channels[i].requeueCurrentlyPipelinedRequests();
382
383
        // send the next request
384
0
        QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
385
0
    }
386
0
}
387
388
void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy)
389
0
{
390
0
    Q_ASSERT(auth);
391
392
    // NTLM and Negotiate do multi-phase authentication.
393
    // Copying credentialsbetween authenticators would mess things up.
394
0
    if (fromChannel >= 0) {
395
0
        QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth);
396
0
        if (priv
397
0
            && (priv->method == QAuthenticatorPrivate::Ntlm
398
0
                || priv->method == QAuthenticatorPrivate::Negotiate)) {
399
0
            return;
400
0
        }
401
0
    }
402
403
    // select another channel
404
0
    QAuthenticator* otherAuth = nullptr;
405
0
    for (int i = 0; i < activeChannelCount; ++i) {
406
0
        if (i == fromChannel)
407
0
            continue;
408
0
        if (isProxy)
409
0
            otherAuth = &channels[i].proxyAuthenticator;
410
0
        else
411
0
            otherAuth = &channels[i].authenticator;
412
        // if the credentials are different, copy them
413
0
        if (otherAuth->user().compare(auth->user()))
414
0
            otherAuth->setUser(auth->user());
415
0
        if (otherAuth->password().compare(auth->password()))
416
0
            otherAuth->setPassword(auth->password());
417
0
    }
418
0
}
419
420
421
// handles the authentication for one channel and eventually re-starts the other channels
422
bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QIODevice *socket, QHttpNetworkReply *reply,
423
                                                                bool isProxy, bool &resend)
424
0
{
425
0
    Q_ASSERT(socket);
426
0
    Q_ASSERT(reply);
427
428
0
    resend = false;
429
    //create the response header to be used with QAuthenticatorPrivate.
430
0
    const auto headers = reply->header();
431
432
    // Check that any of the proposed authenticate methods are supported
433
0
    const QByteArray header = isProxy ? "proxy-authenticate" : "www-authenticate";
434
0
    const QByteArrayList &authenticationMethods = reply->d_func()->headerFieldValues(header);
435
0
    const bool isSupported = std::any_of(authenticationMethods.begin(), authenticationMethods.end(),
436
0
                                         QAuthenticatorPrivate::isMethodSupported);
437
0
    if (isSupported) {
438
0
        int i = indexOf(socket);
439
        //Use a single authenticator for all domains. ### change later to use domain/realm
440
0
        QAuthenticator *auth = isProxy ? &channels[i].proxyAuthenticator
441
0
                                       : &channels[i].authenticator;
442
        //proceed with the authentication.
443
0
        if (auth->isNull())
444
0
            auth->detach();
445
0
        QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth);
446
0
        priv->parseHttpResponse(headers, isProxy);
447
        // Update method in case it changed
448
0
        if (priv->method == QAuthenticatorPrivate::None)
449
0
            return false;
450
451
0
        if (priv->phase == QAuthenticatorPrivate::Done ||
452
0
                (priv->phase == QAuthenticatorPrivate::Start
453
0
                    && (priv->method == QAuthenticatorPrivate::Ntlm
454
0
                        || priv->method == QAuthenticatorPrivate::Negotiate))) {
455
0
            if (priv->phase == QAuthenticatorPrivate::Start)
456
0
                priv->phase = QAuthenticatorPrivate::Phase1;
457
458
0
            pauseConnection();
459
0
            if (!isProxy) {
460
0
                if (channels[i].authenticationCredentialsSent) {
461
0
                    auth->detach();
462
0
                    priv = QAuthenticatorPrivate::getPrivate(*auth);
463
0
                    priv->hasFailed = true;
464
0
                    priv->phase = QAuthenticatorPrivate::Done;
465
0
                    channels[i].authenticationCredentialsSent = false;
466
0
                }
467
0
                emit reply->authenticationRequired(reply->request(), auth);
468
0
#ifndef QT_NO_NETWORKPROXY
469
0
            } else {
470
0
                if (channels[i].proxyCredentialsSent) {
471
0
                    auth->detach();
472
0
                    priv = QAuthenticatorPrivate::getPrivate(*auth);
473
0
                    priv->hasFailed = true;
474
0
                    priv->phase = QAuthenticatorPrivate::Done;
475
0
                    channels[i].proxyCredentialsSent = false;
476
0
                }
477
0
                emit reply->proxyAuthenticationRequired(networkProxy, auth);
478
0
#endif
479
0
            }
480
0
            resumeConnection();
481
482
0
            if (priv->phase != QAuthenticatorPrivate::Done) {
483
                // send any pending requests
484
0
                copyCredentials(i,  auth, isProxy);
485
0
            }
486
0
        } else if (priv->phase == QAuthenticatorPrivate::Start) {
487
            // If the url's authenticator has a 'user' set we will end up here (phase is only set to 'Done' by
488
            // parseHttpResponse above if 'user' is empty). So if credentials were supplied with the request,
489
            // such as in the case of an XMLHttpRequest, this is our only opportunity to cache them.
490
0
            emit reply->cacheCredentials(reply->request(), auth);
491
0
        }
492
        // - Changing values in QAuthenticator will reset the 'phase'. Therefore if it is still "Done"
493
        //   then nothing was filled in by the user or the cache
494
        // - If withCredentials has been set to false (e.g. by Qt WebKit for a cross-origin XMLHttpRequest) then
495
        //   we need to bail out if authentication is required.
496
0
        if (priv->phase == QAuthenticatorPrivate::Done || !reply->request().withCredentials()) {
497
            // Reset authenticator so the next request on that channel does not get messed up
498
0
            auth = nullptr;
499
0
            if (isProxy)
500
0
                channels[i].proxyAuthenticator = QAuthenticator();
501
0
            else
502
0
                channels[i].authenticator = QAuthenticator();
503
504
            // authentication is cancelled, send the current contents to the user.
505
0
            emit reply->headerChanged();
506
0
            emit reply->readyRead();
507
0
            QNetworkReply::NetworkError errorCode =
508
0
                isProxy
509
0
                ? QNetworkReply::ProxyAuthenticationRequiredError
510
0
                : QNetworkReply::AuthenticationRequiredError;
511
0
            reply->d_func()->errorString = errorDetail(errorCode, socket);
512
0
            emit reply->finishedWithError(errorCode, reply->d_func()->errorString);
513
            // ### at this point the reply could be deleted
514
0
            return true;
515
0
        }
516
        //resend the request
517
0
        resend = true;
518
0
        return true;
519
0
    }
520
0
    return false;
521
0
}
522
523
// Used by the HTTP1 code-path
524
QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QIODevice *socket,
525
                                                          QHttpNetworkReply *reply)
526
0
{
527
0
    ParseRedirectResult result = parseRedirectResponse(reply);
528
0
    if (result.errorCode != QNetworkReply::NoError) {
529
0
        emitReplyError(socket, reply, result.errorCode);
530
0
        return {};
531
0
    }
532
0
    return std::move(result.redirectUrl);
533
0
}
534
535
QHttpNetworkConnectionPrivate::ParseRedirectResult
536
QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)
537
0
{
538
0
    if (!reply->request().isFollowRedirects())
539
0
        return {{}, QNetworkReply::NoError};
540
541
0
    QUrl redirectUrl;
542
0
    const QHttpHeaders fields = reply->header();
543
0
    if (const auto h = fields.values(QHttpHeaders::WellKnownHeader::Location); !h.empty()) {
544
0
        redirectUrl = QUrl::fromEncoded(h.first());
545
0
    }
546
547
    // If the location url is invalid/empty, we return ProtocolUnknownError
548
0
    if (!redirectUrl.isValid())
549
0
        return {{}, QNetworkReply::ProtocolUnknownError};
550
551
    // Check if we have exceeded max redirects allowed
552
0
    if (reply->request().redirectCount() <= 0)
553
0
        return {{}, QNetworkReply::TooManyRedirectsError};
554
555
    // Resolve the URL if it's relative
556
0
    if (redirectUrl.isRelative())
557
0
        redirectUrl = reply->request().url().resolved(redirectUrl);
558
559
    // Check redirect url protocol
560
0
    const QUrl priorUrl(reply->request().url());
561
0
    const QString targetUrlScheme = redirectUrl.scheme();
562
0
    if (targetUrlScheme == "http"_L1 || targetUrlScheme == "https"_L1
563
0
        || targetUrlScheme.startsWith("unix"_L1)) {
564
0
        switch (reply->request().redirectPolicy()) {
565
0
        case QNetworkRequest::NoLessSafeRedirectPolicy:
566
            // Here we could handle https->http redirects as InsecureProtocolError.
567
            // However, if HSTS is enabled and redirectUrl.host() is a known STS
568
            // host, then we'll replace its scheme and this won't downgrade protocol,
569
            // after all.  We cannot access QNAM's STS cache from here, so delegate
570
            // this check to QNetworkReplyHttpImpl.
571
0
            break;
572
0
        case QNetworkRequest::SameOriginRedirectPolicy:
573
0
            if (priorUrl.host() != redirectUrl.host()
574
0
                || priorUrl.scheme() != targetUrlScheme
575
0
                || priorUrl.port() != redirectUrl.port()) {
576
0
                return {{}, QNetworkReply::InsecureRedirectError};
577
0
            }
578
0
            break;
579
0
        case QNetworkRequest::UserVerifiedRedirectPolicy:
580
0
            break;
581
0
        default:
582
0
            Q_ASSERT(!"Unexpected redirect policy");
583
0
        }
584
0
    } else {
585
0
        return {{}, QNetworkReply::ProtocolUnknownError};
586
0
    }
587
0
    return {std::move(redirectUrl), QNetworkReply::NoError};
588
0
}
589
590
void QHttpNetworkConnectionPrivate::createAuthorization(QIODevice *socket, QHttpNetworkRequest &request)
591
0
{
592
0
    Q_ASSERT(socket);
593
594
0
    QHttpNetworkConnectionChannel &channel = channels[indexOf(socket)];
595
596
0
    QAuthenticator *authenticator = &channel.authenticator;
597
0
    QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*authenticator);
598
    // Send "Authorization" header, but not if it's NTLM and the socket is already authenticated.
599
0
    if (priv && priv->method != QAuthenticatorPrivate::None) {
600
0
        const bool ntlmNego = priv->method == QAuthenticatorPrivate::Ntlm
601
0
                || priv->method == QAuthenticatorPrivate::Negotiate;
602
0
        const bool authNeeded = channel.lastStatus == 401;
603
0
        const bool ntlmNegoOk = ntlmNego && authNeeded
604
0
                && (priv->phase != QAuthenticatorPrivate::Done
605
0
                    || !channel.authenticationCredentialsSent);
606
0
        const bool otherOk =
607
0
                !ntlmNego && (authNeeded || request.headerField("Authorization").isEmpty());
608
0
        if (ntlmNegoOk || otherOk) {
609
0
            QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false),
610
0
                                                          request.url().host());
611
0
            request.setHeaderField("Authorization", response);
612
0
            channel.authenticationCredentialsSent = true;
613
0
        }
614
0
    }
615
616
0
#if QT_CONFIG(networkproxy)
617
0
    authenticator = &channel.proxyAuthenticator;
618
0
    priv = QAuthenticatorPrivate::getPrivate(*authenticator);
619
    // Send "Proxy-Authorization" header, but not if it's NTLM and the socket is already authenticated.
620
0
    if (priv && priv->method != QAuthenticatorPrivate::None) {
621
0
        const bool ntlmNego = priv->method == QAuthenticatorPrivate::Ntlm
622
0
                || priv->method == QAuthenticatorPrivate::Negotiate;
623
0
        const bool proxyAuthNeeded = channel.lastStatus == 407;
624
0
        const bool ntlmNegoOk = ntlmNego && proxyAuthNeeded
625
0
                && (priv->phase != QAuthenticatorPrivate::Done || !channel.proxyCredentialsSent);
626
0
        const bool otherOk = !ntlmNego;
627
0
        if (ntlmNegoOk || otherOk) {
628
0
            QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false),
629
0
                                                          networkProxy.hostName());
630
0
            request.setHeaderField("Proxy-Authorization", response);
631
0
            channel.proxyCredentialsSent = true;
632
0
        }
633
0
    }
634
0
#endif // QT_CONFIG(networkproxy)
635
0
}
636
637
QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetworkRequest &request)
638
0
{
639
0
    Q_Q(QHttpNetworkConnection);
640
641
    // The reply component of the pair is created initially.
642
0
    QHttpNetworkReply *reply = new QHttpNetworkReply(request.url());
643
0
    reply->setRequest(request);
644
0
    reply->d_func()->connection = q;
645
0
    reply->d_func()->connectionChannel = &channels[0]; // will have the correct one set later
646
0
    HttpMessagePair pair = std::pair(request, reply);
647
648
0
    if (request.isPreConnect())
649
0
        preConnectRequests++;
650
651
0
    if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP
652
0
        || (!encrypt && connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 && !channels[0].switchedToHttp2)) {
653
0
        switch (request.priority()) {
654
0
        case QHttpNetworkRequest::HighPriority:
655
0
            highPriorityQueue.prepend(pair);
656
0
            break;
657
0
        case QHttpNetworkRequest::NormalPriority:
658
0
        case QHttpNetworkRequest::LowPriority:
659
0
            lowPriorityQueue.prepend(pair);
660
0
            break;
661
0
        }
662
0
    }
663
0
    else { // HTTP/2 ('h2' mode)
664
0
        if (!pair.second->d_func()->requestIsPrepared)
665
0
            prepareRequest(pair);
666
0
        channels[0].h2RequestsToSend.insert(request.priority(), pair);
667
0
    }
668
669
    // For Happy Eyeballs the networkLayerState is set to Unknown
670
    // until we have started the first connection attempt. So no
671
    // request will be started until we know if IPv4 or IPv6
672
    // should be used.
673
0
    if (networkLayerState == Unknown || networkLayerState == HostLookupPending) {
674
0
        startHostInfoLookup();
675
0
    } else if ( networkLayerState == IPv4 || networkLayerState == IPv6 ) {
676
        // this used to be called via invokeMethod and a QueuedConnection
677
        // It is the only place _q_startNextRequest is called directly without going
678
        // through the event loop using a QueuedConnection.
679
        // This is dangerous because of recursion that might occur when emitting
680
        // signals as DirectConnection from this code path. Therefore all signal
681
        // emissions that can come out from this code path need to
682
        // be QueuedConnection.
683
        // We are currently trying to fine-tune this.
684
0
        _q_startNextRequest();
685
0
    }
686
0
    return reply;
687
0
}
688
689
void QHttpNetworkConnectionPrivate::fillHttp2Queue()
690
0
{
691
0
    for (auto &pair : highPriorityQueue) {
692
0
        if (!pair.second->d_func()->requestIsPrepared)
693
0
            prepareRequest(pair);
694
0
        channels[0].h2RequestsToSend.insert(QHttpNetworkRequest::HighPriority, pair);
695
0
    }
696
697
0
    highPriorityQueue.clear();
698
699
0
    for (auto &pair : lowPriorityQueue) {
700
0
        if (!pair.second->d_func()->requestIsPrepared)
701
0
            prepareRequest(pair);
702
0
        channels[0].h2RequestsToSend.insert(pair.first.priority(), pair);
703
0
    }
704
705
0
    lowPriorityQueue.clear();
706
0
}
707
708
void QHttpNetworkConnectionPrivate::requeueRequest(const HttpMessagePair &pair)
709
0
{
710
0
    Q_Q(QHttpNetworkConnection);
711
712
0
    QHttpNetworkRequest request = pair.first;
713
0
    switch (request.priority()) {
714
0
    case QHttpNetworkRequest::HighPriority:
715
0
        highPriorityQueue.prepend(pair);
716
0
        break;
717
0
    case QHttpNetworkRequest::NormalPriority:
718
0
    case QHttpNetworkRequest::LowPriority:
719
0
        lowPriorityQueue.prepend(pair);
720
0
        break;
721
0
    }
722
723
0
    QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
724
0
}
725
726
bool QHttpNetworkConnectionPrivate::dequeueRequest(QIODevice *socket)
727
0
{
728
0
    int i = 0;
729
0
    if (socket)
730
0
        i = indexOf(socket);
731
732
0
    if (!highPriorityQueue.isEmpty()) {
733
        // remove from queue before sendRequest! else we might pipeline the same request again
734
0
        HttpMessagePair messagePair = highPriorityQueue.takeLast();
735
0
        if (!messagePair.second->d_func()->requestIsPrepared)
736
0
            prepareRequest(messagePair);
737
0
        updateChannel(i, messagePair);
738
0
        return true;
739
0
    }
740
741
0
    if (!lowPriorityQueue.isEmpty()) {
742
        // remove from queue before sendRequest! else we might pipeline the same request again
743
0
        HttpMessagePair messagePair = lowPriorityQueue.takeLast();
744
0
        if (!messagePair.second->d_func()->requestIsPrepared)
745
0
            prepareRequest(messagePair);
746
0
        updateChannel(i, messagePair);
747
0
        return true;
748
0
    }
749
0
    return false;
750
0
}
751
752
void QHttpNetworkConnectionPrivate::updateChannel(int i, const HttpMessagePair &messagePair)
753
0
{
754
0
    channels[i].request = messagePair.first;
755
0
    channels[i].reply = messagePair.second;
756
    // Now that reply is assigned a channel, correct reply to channel association
757
    // previously set in queueRequest.
758
0
    channels[i].reply->d_func()->connectionChannel = &channels[i];
759
0
}
760
761
QHttpNetworkRequest QHttpNetworkConnectionPrivate::predictNextRequest() const
762
0
{
763
0
    if (!highPriorityQueue.isEmpty())
764
0
        return highPriorityQueue.last().first;
765
0
    if (!lowPriorityQueue.isEmpty())
766
0
        return lowPriorityQueue.last().first;
767
0
    return QHttpNetworkRequest();
768
0
}
769
770
QHttpNetworkReply* QHttpNetworkConnectionPrivate::predictNextRequestsReply() const
771
0
{
772
0
    if (!highPriorityQueue.isEmpty())
773
0
        return highPriorityQueue.last().second;
774
0
    if (!lowPriorityQueue.isEmpty())
775
0
        return lowPriorityQueue.last().second;
776
0
    return nullptr;
777
0
}
778
779
// this is called from _q_startNextRequest and when a request has been sent down a socket from the channel
780
void QHttpNetworkConnectionPrivate::fillPipeline(QIODevice *socket)
781
0
{
782
    // return fast if there is nothing to pipeline
783
0
    if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
784
0
        return;
785
786
0
    int i = indexOf(socket);
787
788
    // return fast if there was no reply right now processed
789
0
    if (channels[i].reply == nullptr)
790
0
        return;
791
792
0
    if (! (defaultPipelineLength - channels[i].alreadyPipelinedRequests.size() >= defaultRePipelineLength)) {
793
0
        return;
794
0
    }
795
796
0
    if (channels[i].pipeliningSupported != QHttpNetworkConnectionChannel::PipeliningProbablySupported)
797
0
        return;
798
799
    // the current request that is in must already support pipelining
800
0
    if (!channels[i].request.isPipeliningAllowed())
801
0
        return;
802
803
    // the current request must be a idempotent (right now we only check GET)
804
0
    if (channels[i].request.operation() != QHttpNetworkRequest::Get)
805
0
        return;
806
807
    // check if socket is connected
808
0
    if (QSocketAbstraction::socketState(socket) != QAbstractSocket::ConnectedState)
809
0
        return;
810
811
    // check for resendCurrent
812
0
    if (channels[i].resendCurrent)
813
0
        return;
814
815
    // we do not like authentication stuff
816
    // ### make sure to be OK with this in later releases
817
0
    if (!channels[i].authenticator.isNull()
818
0
        && (!channels[i].authenticator.user().isEmpty()
819
0
            || !channels[i].authenticator.password().isEmpty()))
820
0
        return;
821
0
    if (!channels[i].proxyAuthenticator.isNull()
822
0
        && (!channels[i].proxyAuthenticator.user().isEmpty()
823
0
            || !channels[i].proxyAuthenticator.password().isEmpty()))
824
0
        return;
825
826
    // must be in ReadingState or WaitingState
827
0
    if (! (channels[i].state == QHttpNetworkConnectionChannel::WaitingState
828
0
           || channels[i].state == QHttpNetworkConnectionChannel::ReadingState))
829
0
        return;
830
831
0
    int lengthBefore;
832
0
    while (!highPriorityQueue.isEmpty()) {
833
0
        lengthBefore = channels[i].alreadyPipelinedRequests.size();
834
0
        fillPipeline(highPriorityQueue, channels[i]);
835
836
0
        if (channels[i].alreadyPipelinedRequests.size() >= defaultPipelineLength) {
837
0
            channels[i].pipelineFlush();
838
0
            return;
839
0
        }
840
841
0
        if (lengthBefore == channels[i].alreadyPipelinedRequests.size())
842
0
            break; // did not process anything, now do the low prio queue
843
0
    }
844
845
0
    while (!lowPriorityQueue.isEmpty()) {
846
0
        lengthBefore = channels[i].alreadyPipelinedRequests.size();
847
0
        fillPipeline(lowPriorityQueue, channels[i]);
848
849
0
        if (channels[i].alreadyPipelinedRequests.size() >= defaultPipelineLength) {
850
0
            channels[i].pipelineFlush();
851
0
            return;
852
0
        }
853
854
0
        if (lengthBefore == channels[i].alreadyPipelinedRequests.size())
855
0
            break; // did not process anything
856
0
    }
857
858
859
0
    channels[i].pipelineFlush();
860
0
}
861
862
// returns true when the processing of a queue has been done
863
bool QHttpNetworkConnectionPrivate::fillPipeline(QList<HttpMessagePair> &queue, QHttpNetworkConnectionChannel &channel)
864
0
{
865
0
    if (queue.isEmpty())
866
0
        return true;
867
868
0
    for (int i = queue.size() - 1; i >= 0; --i) {
869
0
        HttpMessagePair messagePair = queue.at(i);
870
0
        const QHttpNetworkRequest &request = messagePair.first;
871
872
        // we currently do not support pipelining if HTTP authentication is used
873
0
        if (!request.url().userInfo().isEmpty())
874
0
            continue;
875
876
        // take only GET requests
877
0
        if (request.operation() != QHttpNetworkRequest::Get)
878
0
            continue;
879
880
0
        if (!request.isPipeliningAllowed())
881
0
            continue;
882
883
        // remove it from the queue
884
0
        queue.takeAt(i);
885
        // we modify the queue we iterate over here, but since we return from the function
886
        // afterwards this is fine.
887
888
        // actually send it
889
0
        if (!messagePair.second->d_func()->requestIsPrepared)
890
0
            prepareRequest(messagePair);
891
0
        channel.pipelineInto(messagePair);
892
893
        // return false because we processed something and need to process again
894
0
        return false;
895
0
    }
896
897
    // return true, the queue has been processed and not changed
898
0
    return true;
899
0
}
900
901
902
QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QIODevice *socket, const QString &extraDetail)
903
0
{
904
0
    QString errorString;
905
0
    switch (errorCode) {
906
0
    case QNetworkReply::HostNotFoundError: {
907
0
        const QString peerName = socket ? QSocketAbstraction::socketPeerName(socket) : hostName;
908
0
        errorString = QCoreApplication::translate("QHttp", "Host %1 not found").arg(peerName);
909
0
        break;
910
0
    }
911
0
    case QNetworkReply::ConnectionRefusedError:
912
0
        errorString = QCoreApplication::translate("QHttp", "Connection refused");
913
0
        break;
914
0
    case QNetworkReply::RemoteHostClosedError:
915
0
        errorString = QCoreApplication::translate("QHttp", "Connection closed");
916
0
        break;
917
0
    case QNetworkReply::TimeoutError:
918
0
        errorString = QCoreApplication::translate("QAbstractSocket", "Socket operation timed out");
919
0
        break;
920
0
    case QNetworkReply::ProxyAuthenticationRequiredError:
921
0
        errorString = QCoreApplication::translate("QHttp", "Proxy requires authentication");
922
0
        break;
923
0
    case QNetworkReply::AuthenticationRequiredError:
924
0
        errorString = QCoreApplication::translate("QHttp", "Host requires authentication");
925
0
        break;
926
0
    case QNetworkReply::ProtocolFailure:
927
0
        errorString = QCoreApplication::translate("QHttp", "Data corrupted");
928
0
        break;
929
0
    case QNetworkReply::ProtocolUnknownError:
930
0
        errorString = QCoreApplication::translate("QHttp", "Unknown protocol specified");
931
0
        break;
932
0
    case QNetworkReply::SslHandshakeFailedError:
933
0
        errorString = QCoreApplication::translate("QHttp", "SSL handshake failed");
934
0
        if (socket)
935
0
            errorString += ": "_L1 + socket->errorString();
936
0
        break;
937
0
    case QNetworkReply::TooManyRedirectsError:
938
0
        errorString = QCoreApplication::translate("QHttp", "Too many redirects");
939
0
        break;
940
0
    case QNetworkReply::InsecureRedirectError:
941
0
        errorString = QCoreApplication::translate("QHttp", "Insecure redirect");
942
0
        break;
943
0
    default:
944
        // all other errors are treated as QNetworkReply::UnknownNetworkError
945
0
        errorString = extraDetail;
946
0
        break;
947
0
    }
948
0
    return errorString;
949
0
}
950
951
// this is called from the destructor of QHttpNetworkReply. It is called when
952
// the reply was finished correctly or when it was aborted.
953
void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply)
954
0
{
955
0
    Q_Q(QHttpNetworkConnection);
956
957
    // check if the reply is currently being processed or it is pipelined in
958
0
    for (int i = 0; i < activeChannelCount; ++i) {
959
        // is the reply associated the currently processing of this channel?
960
0
        if (channels[i].reply == reply) {
961
0
            channels[i].reply = nullptr;
962
0
            if (channels[i].protocolHandler)
963
0
                channels[i].protocolHandler->setReply(nullptr);
964
0
            channels[i].request = QHttpNetworkRequest();
965
0
            channels[i].resendCurrent = false;
966
967
0
            if (!reply->isFinished() && !channels[i].alreadyPipelinedRequests.isEmpty()) {
968
                // the reply had to be prematurely removed, e.g. it was not finished
969
                // therefore we have to requeue the already pipelined requests.
970
0
                channels[i].requeueCurrentlyPipelinedRequests();
971
0
            }
972
973
            // if HTTP mandates we should close
974
            // or the reply is not finished yet, e.g. it was aborted
975
            // we have to close that connection
976
0
            if (reply->d_func()->isConnectionCloseEnabled() || !reply->isFinished()) {
977
0
                if (reply->isAborted()) {
978
0
                    channels[i].abort();
979
0
                } else {
980
0
                    channels[i].close();
981
0
                }
982
0
            }
983
984
0
            QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
985
0
            return;
986
0
        }
987
988
        // is the reply inside the pipeline of this channel already?
989
0
        for (int j = 0; j < channels[i].alreadyPipelinedRequests.size(); j++) {
990
0
            if (channels[i].alreadyPipelinedRequests.at(j).second == reply) {
991
               // Remove that HttpMessagePair
992
0
               channels[i].alreadyPipelinedRequests.removeAt(j);
993
994
0
               channels[i].requeueCurrentlyPipelinedRequests();
995
996
               // Since some requests had already been pipelined, but we removed
997
               // one and re-queued the others
998
               // we must force a connection close after the request that is
999
               // currently in processing has been finished.
1000
0
               if (channels[i].reply)
1001
0
                   channels[i].reply->d_func()->forceConnectionCloseEnabled = true;
1002
1003
0
               QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
1004
0
               return;
1005
0
            }
1006
0
        }
1007
        // is the reply inside the H2 pipeline of this channel already?
1008
0
        const auto foundReply = [reply](const HttpMessagePair &pair) {
1009
0
            return pair.second == reply;
1010
0
        };
1011
0
        auto &seq = channels[i].h2RequestsToSend;
1012
0
        const auto end = seq.cend();
1013
0
        auto it = std::find_if(seq.cbegin(), end, foundReply);
1014
0
        if (it != end) {
1015
0
            seq.erase(it);
1016
0
            QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
1017
0
            return;
1018
0
        }
1019
        // Check if the h2 protocol handler already started processing it
1020
0
        if ((connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct
1021
0
             || channels[i].switchedToHttp2)
1022
0
            && channels[i].protocolHandler) {
1023
0
            if (channels[i].protocolHandler->tryRemoveReply(reply))
1024
0
                return;
1025
0
        }
1026
0
    }
1027
    // remove from the high priority queue
1028
0
    if (!highPriorityQueue.isEmpty()) {
1029
0
        for (int j = highPriorityQueue.size() - 1; j >= 0; --j) {
1030
0
            HttpMessagePair messagePair = highPriorityQueue.at(j);
1031
0
            if (messagePair.second == reply) {
1032
0
                highPriorityQueue.removeAt(j);
1033
0
                QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
1034
0
                return;
1035
0
            }
1036
0
        }
1037
0
    }
1038
    // remove from the low priority queue
1039
0
    if (!lowPriorityQueue.isEmpty()) {
1040
0
        for (int j = lowPriorityQueue.size() - 1; j >= 0; --j) {
1041
0
            HttpMessagePair messagePair = lowPriorityQueue.at(j);
1042
0
            if (messagePair.second == reply) {
1043
0
                lowPriorityQueue.removeAt(j);
1044
0
                QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
1045
0
                return;
1046
0
            }
1047
0
        }
1048
0
    }
1049
0
}
1050
1051
1052
1053
// This function must be called from the event loop. The only
1054
// exception is documented in QHttpNetworkConnectionPrivate::queueRequest
1055
// although it is called _q_startNextRequest, it will actually start multiple requests when possible
1056
void QHttpNetworkConnectionPrivate::_q_startNextRequest()
1057
0
{
1058
    // If there is no network layer state decided we should not start any new requests.
1059
0
    if (networkLayerState == Unknown || networkLayerState == HostLookupPending || networkLayerState == IPv4or6)
1060
0
        return;
1061
1062
    // If the QHttpNetworkConnection is currently paused then bail out immediately
1063
0
    if (state == PausedState)
1064
0
        return;
1065
1066
    //resend the necessary ones.
1067
0
    for (int i = 0; i < activeChannelCount; ++i) {
1068
0
        if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) {
1069
0
            if (!channels[i].socket
1070
0
                || QSocketAbstraction::socketState(channels[i].socket) == QAbstractSocket::UnconnectedState) {
1071
0
                if (!channels[i].ensureConnection())
1072
0
                    continue;
1073
0
            }
1074
0
            channels[i].resendCurrent = false;
1075
1076
            // if this is not possible, error will be emitted and connection terminated
1077
0
            if (!channels[i].resetUploadData())
1078
0
                continue;
1079
0
            channels[i].sendRequest();
1080
0
        }
1081
0
    }
1082
1083
    // dequeue new ones
1084
1085
0
    switch (connectionType) {
1086
0
    case QHttpNetworkConnection::ConnectionTypeHTTP: {
1087
        // return fast if there is nothing to do
1088
0
        if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
1089
0
            return;
1090
1091
        // try to get a free AND connected socket
1092
0
        for (int i = 0; i < activeChannelCount; ++i) {
1093
0
            if (channels[i].socket) {
1094
0
                if (!channels[i].reply && !channels[i].isSocketBusy()
1095
0
                    && QSocketAbstraction::socketState(channels[i].socket)
1096
0
                            == QAbstractSocket::ConnectedState) {
1097
0
                    if (dequeueRequest(channels[i].socket))
1098
0
                        channels[i].sendRequest();
1099
0
                }
1100
0
            }
1101
0
        }
1102
0
        break;
1103
0
    }
1104
0
    case QHttpNetworkConnection::ConnectionTypeHTTP2Direct:
1105
0
    case QHttpNetworkConnection::ConnectionTypeHTTP2: {
1106
0
        auto &channel = channels[0];
1107
0
        if (channel.h2RequestsToSend.isEmpty() && !channel.reply
1108
0
            && highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) {
1109
0
            return;
1110
0
        }
1111
1112
0
        if (networkLayerState == IPv4)
1113
0
            channel.networkLayerPreference = QAbstractSocket::IPv4Protocol;
1114
0
        else if (networkLayerState == IPv6)
1115
0
            channel.networkLayerPreference = QAbstractSocket::IPv6Protocol;
1116
0
        channel.ensureConnection();
1117
1118
        // Connection is going away: hold new requests until the channel reconnects.
1119
0
        if (channel.isPendingReconnect())
1120
0
            return;
1121
1122
0
        if (auto *s = channel.socket; s
1123
0
            && QSocketAbstraction::socketState(s) == QAbstractSocket::ConnectedState
1124
0
            && !channel.pendingEncrypt) {
1125
0
            if (channel.h2RequestsToSend.size()) {
1126
0
                channel.sendRequest();
1127
0
            } else if (!channel.reply && !channel.switchedToHttp2) {
1128
                // This covers an edge-case where we're already connected and the "connected"
1129
                // signal was already sent, but we didn't have any request available at the time,
1130
                // so it was missed. As such we need to dequeue a request and send it now that we
1131
                // have one.
1132
0
                dequeueRequest(channel.socket);
1133
0
                channel.sendRequest();
1134
0
            }
1135
0
        }
1136
0
        break;
1137
0
    }
1138
0
    }
1139
1140
    // try to push more into all sockets
1141
    // ### FIXME we should move this to the beginning of the function
1142
    // as soon as QtWebkit is properly using the pipelining
1143
    // (e.g. not for XMLHttpRequest or the first page load)
1144
    // ### FIXME we should also divide the requests more even
1145
    // on the connected sockets
1146
    //tryToFillPipeline(socket);
1147
    // return fast if there is nothing to pipeline
1148
0
    if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
1149
0
        return;
1150
0
    for (int i = 0; i < activeChannelCount; i++) {
1151
0
        if (channels[i].socket
1152
0
            && QSocketAbstraction::socketState(channels[i].socket)
1153
0
                    == QAbstractSocket::ConnectedState) {
1154
0
            fillPipeline(channels[i].socket);
1155
0
        }
1156
0
    }
1157
1158
    // If there is not already any connected channels we need to connect a new one.
1159
    // We do not pair the channel with the request until we know if it is
1160
    // connected or not. This is to reuse connected channels before we connect new once.
1161
0
    int queuedRequests = highPriorityQueue.size() + lowPriorityQueue.size();
1162
1163
    // in case we have in-flight preconnect requests and normal requests,
1164
    // we only need one socket for each (preconnect, normal request) pair
1165
0
    int neededOpenChannels = queuedRequests;
1166
0
    if (preConnectRequests > 0) {
1167
0
        int normalRequests = queuedRequests - preConnectRequests;
1168
0
        neededOpenChannels = qMax(normalRequests, preConnectRequests);
1169
0
    }
1170
1171
0
    if (neededOpenChannels <= 0)
1172
0
        return;
1173
1174
0
    QVarLengthArray<int> channelsToConnect;
1175
1176
    // use previously used channels first
1177
0
    for (int i = 0; i < activeChannelCount && neededOpenChannels > 0; ++i) {
1178
0
        if (!channels[i].socket)
1179
0
            continue;
1180
1181
0
        using State = QAbstractSocket::SocketState;
1182
0
        if ((QSocketAbstraction::socketState(channels[i].socket) == State::ConnectingState)
1183
0
            || (QSocketAbstraction::socketState(channels[i].socket) == State::HostLookupState)
1184
0
            || channels[i].pendingEncrypt) { // pendingEncrypt == "EncryptingState"
1185
0
            neededOpenChannels--;
1186
0
            continue;
1187
0
        }
1188
1189
0
        if (!channels[i].reply && !channels[i].isSocketBusy()
1190
0
            && (QSocketAbstraction::socketState(channels[i].socket) == State::UnconnectedState)) {
1191
0
            channelsToConnect.push_back(i);
1192
0
            neededOpenChannels--;
1193
0
        }
1194
0
    }
1195
1196
    // use other channels
1197
0
    for (int i = 0; i < activeChannelCount && neededOpenChannels > 0; ++i) {
1198
0
        if (channels[i].socket)
1199
0
            continue;
1200
1201
0
        channelsToConnect.push_back(i);
1202
0
        neededOpenChannels--;
1203
0
    }
1204
1205
0
    auto channelToConnectSpan = QSpan{channelsToConnect};
1206
0
    while (!channelToConnectSpan.isEmpty()) {
1207
0
        const int channel = channelToConnectSpan.front();
1208
0
        channelToConnectSpan = channelToConnectSpan.sliced(1);
1209
1210
0
        if (networkLayerState == IPv4)
1211
0
            channels[channel].networkLayerPreference = QAbstractSocket::IPv4Protocol;
1212
0
        else if (networkLayerState == IPv6)
1213
0
            channels[channel].networkLayerPreference = QAbstractSocket::IPv6Protocol;
1214
1215
0
        channels[channel].ensureConnection();
1216
0
    }
1217
0
}
1218
1219
1220
void QHttpNetworkConnectionPrivate::readMoreLater(QHttpNetworkReply *reply)
1221
0
{
1222
0
    for (int i = 0 ; i < activeChannelCount; ++i) {
1223
0
        if (channels[i].reply ==  reply) {
1224
            // emulate a readyRead() from the socket
1225
0
            QMetaObject::invokeMethod(&channels[i], "_q_readyRead", Qt::QueuedConnection);
1226
0
            return;
1227
0
        }
1228
0
    }
1229
0
}
1230
1231
1232
1233
// The first time we start the connection is used we do not know if we
1234
// should use IPv4 or IPv6. So we start a hostlookup to figure this out.
1235
// Later when we do the connection the socket will not need to do another
1236
// lookup as then the hostinfo will already be in the cache.
1237
void QHttpNetworkConnectionPrivate::startHostInfoLookup()
1238
0
{
1239
0
    networkLayerState = HostLookupPending;
1240
1241
    // check if we already now can decide if this is IPv4 or IPv6
1242
0
    QString lookupHost = hostName;
1243
0
#ifndef QT_NO_NETWORKPROXY
1244
0
    if (networkProxy.capabilities() & QNetworkProxy::HostNameLookupCapability) {
1245
0
        lookupHost = networkProxy.hostName();
1246
0
    } else if (channels[0].proxy.capabilities() & QNetworkProxy::HostNameLookupCapability) {
1247
0
        lookupHost = channels[0].proxy.hostName();
1248
0
    }
1249
0
#endif
1250
0
    QHostAddress temp;
1251
0
    if (temp.setAddress(lookupHost)) {
1252
0
        const QAbstractSocket::NetworkLayerProtocol protocol = temp.protocol();
1253
0
        if (protocol == QAbstractSocket::IPv4Protocol) {
1254
0
            networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
1255
0
            QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
1256
0
            return;
1257
0
        } else if (protocol == QAbstractSocket::IPv6Protocol) {
1258
0
            networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
1259
0
            QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
1260
0
            return;
1261
0
        }
1262
0
    } else {
1263
0
        int hostLookupId;
1264
0
        bool immediateResultValid = false;
1265
0
        QHostInfo hostInfo = qt_qhostinfo_lookup(lookupHost,
1266
0
                                                 this->q_func(),
1267
0
                                                 SLOT(_q_hostLookupFinished(QHostInfo)),
1268
0
                                                 &immediateResultValid,
1269
0
                                                 &hostLookupId);
1270
0
        if (immediateResultValid) {
1271
0
            _q_hostLookupFinished(hostInfo);
1272
0
        }
1273
0
    }
1274
0
}
1275
1276
1277
void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(const QHostInfo &info)
1278
0
{
1279
0
    bool bIpv4 = false;
1280
0
    bool bIpv6 = false;
1281
0
    bool foundAddress = false;
1282
0
    if (networkLayerState == IPv4 || networkLayerState == IPv6 || networkLayerState == IPv4or6)
1283
0
        return;
1284
1285
0
    const auto addresses = info.addresses();
1286
0
    for (const QHostAddress &address : addresses) {
1287
0
        const QAbstractSocket::NetworkLayerProtocol protocol = address.protocol();
1288
0
        if (protocol == QAbstractSocket::IPv4Protocol) {
1289
0
            if (!foundAddress) {
1290
0
                foundAddress = true;
1291
0
                delayIpv4 = false;
1292
0
            }
1293
0
            bIpv4 = true;
1294
0
        } else if (protocol == QAbstractSocket::IPv6Protocol) {
1295
0
            if (!foundAddress) {
1296
0
                foundAddress = true;
1297
0
                delayIpv4 = true;
1298
0
            }
1299
0
            bIpv6 = true;
1300
0
        }
1301
0
    }
1302
1303
0
    if (bIpv4 && bIpv6)
1304
0
        startNetworkLayerStateLookup();
1305
0
    else if (bIpv4) {
1306
0
        networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
1307
0
        QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
1308
0
    } else if (bIpv6) {
1309
0
        networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
1310
0
        QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
1311
0
    } else {
1312
0
        auto lookupError = QNetworkReply::HostNotFoundError;
1313
0
#ifndef QT_NO_NETWORKPROXY
1314
        // if the proxy can lookup hostnames, all hostname lookups except for the lookup of the
1315
        // proxy hostname are delegated to the proxy.
1316
0
        auto proxyCapabilities = networkProxy.capabilities() | channels[0].proxy.capabilities();
1317
0
        if (proxyCapabilities & QNetworkProxy::HostNameLookupCapability)
1318
0
            lookupError = QNetworkReply::ProxyNotFoundError;
1319
0
#endif
1320
0
        if (dequeueRequest(channels[0].socket)) {
1321
0
            emitReplyError(channels[0].socket, channels[0].reply, lookupError);
1322
0
            networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
1323
0
        } else if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
1324
0
                   || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
1325
0
            for (const HttpMessagePair &h2Pair : std::as_const(channels[0].h2RequestsToSend)) {
1326
                // emit error for all replies
1327
0
                QHttpNetworkReply *currentReply = h2Pair.second;
1328
0
                Q_ASSERT(currentReply);
1329
0
                emitReplyError(channels[0].socket, currentReply, lookupError);
1330
0
            }
1331
0
        } else {
1332
            // We can end up here if a request has been aborted or otherwise failed (e.g. timeout)
1333
            // before the host lookup was finished.
1334
0
            qDebug("QHttpNetworkConnectionPrivate::_q_hostLookupFinished"
1335
0
                   " could not de-queue request, failed to report HostNotFoundError");
1336
0
            networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
1337
0
        }
1338
0
    }
1339
0
}
1340
1341
1342
// This will be used if the host lookup found both and Ipv4 and
1343
// Ipv6 address. Then we will start up two connections and pick
1344
// the network layer of the one that finish first. The second
1345
// connection will then be disconnected.
1346
void QHttpNetworkConnectionPrivate::startNetworkLayerStateLookup()
1347
0
{
1348
0
    if (activeChannelCount > 1) {
1349
        // At this time all channels should be unconnected.
1350
0
        Q_ASSERT(!channels[0].isSocketBusy());
1351
0
        Q_ASSERT(!channels[1].isSocketBusy());
1352
1353
0
        networkLayerState = IPv4or6;
1354
1355
0
        channels[0].networkLayerPreference = QAbstractSocket::IPv4Protocol;
1356
0
        channels[1].networkLayerPreference = QAbstractSocket::IPv6Protocol;
1357
1358
0
        int timeout = 300;
1359
0
        delayedConnectionTimer.start(timeout);
1360
0
        if (delayIpv4)
1361
0
            channels[1].ensureConnection();
1362
0
        else
1363
0
            channels[0].ensureConnection();
1364
0
    } else {
1365
0
        networkLayerState = IPv4or6;
1366
0
        channels[0].networkLayerPreference = QAbstractSocket::AnyIPProtocol;
1367
0
        channels[0].ensureConnection();
1368
0
    }
1369
0
}
1370
1371
void QHttpNetworkConnectionPrivate::networkLayerDetected(QAbstractSocket::NetworkLayerProtocol protocol)
1372
0
{
1373
0
    for (int i = 0 ; i < activeChannelCount; ++i) {
1374
0
        if ((channels[i].networkLayerPreference != protocol) && (channels[i].state == QHttpNetworkConnectionChannel::ConnectingState)) {
1375
0
            channels[i].close();
1376
0
        }
1377
0
    }
1378
0
}
1379
1380
void QHttpNetworkConnectionPrivate::_q_connectDelayedChannel()
1381
0
{
1382
0
    if (delayIpv4)
1383
0
        channels[0].ensureConnection();
1384
0
    else
1385
0
        channels[1].ensureConnection();
1386
0
}
1387
1388
QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName,
1389
                                               quint16 port, bool encrypt, bool isLocalSocket, QObject *parent,
1390
                                               QHttpNetworkConnection::ConnectionType connectionType)
1391
0
     : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt, isLocalSocket,
1392
0
                                                   connectionType)), parent)
1393
0
{
1394
0
    Q_D(QHttpNetworkConnection);
1395
0
    d->init();
1396
0
}
1397
1398
QHttpNetworkConnection::~QHttpNetworkConnection()
1399
0
{
1400
0
}
1401
1402
QString QHttpNetworkConnection::hostName() const
1403
0
{
1404
0
    Q_D(const QHttpNetworkConnection);
1405
0
    return d->hostName;
1406
0
}
1407
1408
quint16 QHttpNetworkConnection::port() const
1409
0
{
1410
0
    Q_D(const QHttpNetworkConnection);
1411
0
    return d->port;
1412
0
}
1413
1414
QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest &request)
1415
0
{
1416
0
    Q_D(QHttpNetworkConnection);
1417
0
    return d->queueRequest(request);
1418
0
}
1419
1420
void QHttpNetworkConnection::fillHttp2Queue()
1421
0
{
1422
0
    Q_D(QHttpNetworkConnection);
1423
0
    d->fillHttp2Queue();
1424
0
}
1425
1426
bool QHttpNetworkConnection::isSsl() const
1427
0
{
1428
0
    Q_D(const QHttpNetworkConnection);
1429
0
    return d->encrypt;
1430
0
}
1431
1432
QHttpNetworkConnectionChannel *QHttpNetworkConnection::channels() const
1433
0
{
1434
0
    return d_func()->channels;
1435
0
}
1436
1437
#ifndef QT_NO_NETWORKPROXY
1438
void QHttpNetworkConnection::setCacheProxy(const QNetworkProxy &networkProxy)
1439
0
{
1440
0
    Q_D(QHttpNetworkConnection);
1441
0
    d->networkProxy = networkProxy;
1442
    // update the authenticator
1443
0
    if (!d->networkProxy.user().isEmpty()) {
1444
0
        for (int i = 0; i < d->channelCount; ++i) {
1445
0
            d->channels[i].proxyAuthenticator.setUser(d->networkProxy.user());
1446
0
            d->channels[i].proxyAuthenticator.setPassword(d->networkProxy.password());
1447
0
        }
1448
0
    }
1449
0
}
1450
1451
QNetworkProxy QHttpNetworkConnection::cacheProxy() const
1452
0
{
1453
0
    Q_D(const QHttpNetworkConnection);
1454
0
    return d->networkProxy;
1455
0
}
1456
1457
void QHttpNetworkConnection::setTransparentProxy(const QNetworkProxy &networkProxy)
1458
0
{
1459
0
    Q_D(QHttpNetworkConnection);
1460
0
    for (int i = 0; i < d->channelCount; ++i)
1461
0
        d->channels[i].setProxy(networkProxy);
1462
0
}
1463
1464
QNetworkProxy QHttpNetworkConnection::transparentProxy() const
1465
0
{
1466
0
    Q_D(const QHttpNetworkConnection);
1467
0
    return d->channels[0].proxy;
1468
0
}
1469
#endif
1470
1471
QHttpNetworkConnection::ConnectionType QHttpNetworkConnection::connectionType() const
1472
0
{
1473
0
    Q_D(const QHttpNetworkConnection);
1474
0
    return d->connectionType;
1475
0
}
1476
1477
void QHttpNetworkConnection::setConnectionType(ConnectionType type)
1478
0
{
1479
0
    Q_D(QHttpNetworkConnection);
1480
0
    d->connectionType = type;
1481
0
}
1482
1483
QHttp2Configuration QHttpNetworkConnection::http2Parameters() const
1484
0
{
1485
0
    Q_D(const QHttpNetworkConnection);
1486
0
    return d->http2Parameters;
1487
0
}
1488
1489
void QHttpNetworkConnection::setHttp2Parameters(const QHttp2Configuration &params)
1490
0
{
1491
0
    Q_D(QHttpNetworkConnection);
1492
0
    d->http2Parameters = params;
1493
0
}
1494
1495
QTcpKeepAliveConfiguration QHttpNetworkConnection::tcpKeepAliveParameters() const
1496
0
{
1497
0
    Q_D(const QHttpNetworkConnection);
1498
0
    return d->tcpKeepAliveConfiguration;
1499
0
}
1500
1501
void QHttpNetworkConnection::setTcpKeepAliveParameters(QTcpKeepAliveConfiguration config)
1502
0
{
1503
0
    Q_D(QHttpNetworkConnection);
1504
0
    d->tcpKeepAliveConfiguration = config;
1505
0
}
1506
1507
// SSL support below
1508
#ifndef QT_NO_SSL
1509
void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config)
1510
0
{
1511
0
    Q_D(QHttpNetworkConnection);
1512
0
    if (!d->encrypt)
1513
0
        return;
1514
1515
    // set the config on all channels
1516
0
    for (int i = 0; i < d->activeChannelCount; ++i)
1517
0
        d->channels[i].setSslConfiguration(config);
1518
0
}
1519
1520
std::shared_ptr<QSslContext> QHttpNetworkConnection::sslContext() const
1521
0
{
1522
0
    Q_D(const QHttpNetworkConnection);
1523
0
    return d->sslContext;
1524
0
}
1525
1526
void QHttpNetworkConnection::setSslContext(std::shared_ptr<QSslContext> context)
1527
0
{
1528
0
    Q_D(QHttpNetworkConnection);
1529
0
    d->sslContext = std::move(context);
1530
0
}
1531
1532
void QHttpNetworkConnection::ignoreSslErrors(int channel)
1533
0
{
1534
0
    Q_D(QHttpNetworkConnection);
1535
0
    if (!d->encrypt)
1536
0
        return;
1537
1538
0
    if (channel == -1) { // ignore for all channels
1539
        // We need to ignore for all channels, even the ones that are not in use just in case they
1540
        // will be in the future.
1541
0
        for (int i = 0; i < d->channelCount; ++i) {
1542
0
            d->channels[i].ignoreSslErrors();
1543
0
        }
1544
1545
0
    } else {
1546
0
        d->channels[channel].ignoreSslErrors();
1547
0
    }
1548
0
}
1549
1550
void QHttpNetworkConnection::ignoreSslErrors(const QList<QSslError> &errors, int channel)
1551
0
{
1552
0
    Q_D(QHttpNetworkConnection);
1553
0
    if (!d->encrypt)
1554
0
        return;
1555
1556
0
    if (channel == -1) { // ignore for all channels
1557
        // We need to ignore for all channels, even the ones that are not in use just in case they
1558
        // will be in the future.
1559
0
        for (int i = 0; i < d->channelCount; ++i) {
1560
0
            d->channels[i].ignoreSslErrors(errors);
1561
0
        }
1562
1563
0
    } else {
1564
0
        d->channels[channel].ignoreSslErrors(errors);
1565
0
    }
1566
0
}
1567
1568
#endif //QT_NO_SSL
1569
1570
void QHttpNetworkConnection::preConnectFinished()
1571
0
{
1572
0
    d_func()->preConnectRequests--;
1573
0
}
1574
1575
QString QHttpNetworkConnection::peerVerifyName() const
1576
0
{
1577
0
    Q_D(const QHttpNetworkConnection);
1578
0
    return d->peerVerifyName;
1579
0
}
1580
1581
void QHttpNetworkConnection::setPeerVerifyName(const QString &peerName)
1582
0
{
1583
0
    Q_D(QHttpNetworkConnection);
1584
0
    d->peerVerifyName = peerName;
1585
0
}
1586
1587
void QHttpNetworkConnection::onlineStateChanged(bool isOnline)
1588
0
{
1589
0
    Q_D(QHttpNetworkConnection);
1590
1591
0
    if (isOnline) {
1592
        // If we did not have any 'isOffline' previously - well, good
1593
        // to know, we are 'online' apparently.
1594
0
        return;
1595
0
    }
1596
1597
0
    for (int i = 0; i < d->activeChannelCount; i++) {
1598
0
        auto &channel = d->channels[i];
1599
0
        channel.emitFinishedWithError(QNetworkReply::TemporaryNetworkFailureError, "Temporary network failure.");
1600
0
        channel.close();
1601
0
    }
1602
0
}
1603
1604
#ifndef QT_NO_NETWORKPROXY
1605
// only called from QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired, not
1606
// from QHttpNetworkConnectionChannel::handleAuthenticationChallenge
1607
// e.g. it is for SOCKS proxies which require authentication.
1608
void QHttpNetworkConnectionPrivate::emitProxyAuthenticationRequired(const QHttpNetworkConnectionChannel *chan, const QNetworkProxy &proxy, QAuthenticator* auth)
1609
0
{
1610
    // Also pause the connection because socket notifiers may fire while an user
1611
    // dialog is displaying
1612
0
    pauseConnection();
1613
0
    QHttpNetworkReply *reply;
1614
0
    if ((connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
1615
0
         && (chan->switchedToHttp2 || chan->h2RequestsToSend.size() > 0))
1616
0
        || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
1617
        // we choose the reply to emit the proxyAuth signal from somewhat arbitrarily,
1618
        // but that does not matter because the signal will ultimately be emitted
1619
        // by the QNetworkAccessManager.
1620
0
        Q_ASSERT(chan->h2RequestsToSend.size() > 0);
1621
0
        reply = chan->h2RequestsToSend.cbegin().value().second;
1622
0
    } else { // HTTP
1623
0
        reply = chan->reply;
1624
0
    }
1625
1626
    Q_ASSERT(reply);
1627
0
    emit reply->proxyAuthenticationRequired(proxy, auth);
1628
0
    resumeConnection();
1629
0
    int i = indexOf(chan->socket);
1630
0
    copyCredentials(i, auth, true);
1631
0
}
1632
#endif
1633
1634
1635
QT_END_NAMESPACE
1636
1637
#include "moc_qhttpnetworkconnection_p.cpp"