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