1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2014 BlackBerry Limited. All rights reserved.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:network-protocol
5
6#include "qhttpnetworkconnectionchannel_p.h"
7#include "qhttpnetworkconnection_p.h"
8#include "qhttp2configuration.h"
9#include "private/qnoncontiguousbytedevice_p.h"
10
11#include <qdebug.h>
12
13#include <private/qhttp2protocolhandler_p.h>
14#include <private/qhttpprotocolhandler_p.h>
15#include <private/http2protocol_p.h>
16#include <private/qsocketabstraction_p.h>
17
18#ifndef QT_NO_SSL
19# include <private/qsslsocket_p.h>
20# include <QtNetwork/qsslkey.h>
21# include <QtNetwork/qsslcipher.h>
22#endif
23
24#include "private/qnetconmonitor_p.h"
25
26#include <QtNetwork/private/qtnetworkglobal_p.h>
27
28#include <memory>
29#include <utility>
30
31QT_BEGIN_NAMESPACE
32
33// TODO: Put channel specific stuff here so it does not pollute qhttpnetworkconnection.cpp
34
35// Because in-flight when sending a request, the server might close our connection (because the persistent HTTP
36// connection times out)
37// We use 3 because we can get a _q_error 3 times depending on the timing:
38static const int reconnectAttemptsDefault = 3;
39
40QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel()
41 : socket(nullptr)
42 , ssl(false)
43 , isInitialized(false)
44 , state(IdleState)
45 , reply(nullptr)
46 , written(0)
47 , bytesTotal(0)
48 , resendCurrent(false)
49 , lastStatus(0)
50 , pendingEncrypt(false)
51 , reconnectAttempts(reconnectAttemptsDefault)
52 , authenticationCredentialsSent(false)
53 , proxyCredentialsSent(false)
54 , protocolHandler(nullptr)
55#ifndef QT_NO_SSL
56 , ignoreAllSslErrors(false)
57#endif
58 , pipeliningSupported(PipeliningSupportUnknown)
59 , networkLayerPreference(QAbstractSocket::AnyIPProtocol)
60 , connection(nullptr)
61{
62 // Inlining this function in the header leads to compiler error on
63 // release-armv5, on at least timebox 9.2 and 10.1.
64}
65
66void QHttpNetworkConnectionChannel::init()
67{
68#ifndef QT_NO_SSL
69 if (connection->d_func()->encrypt)
70 socket = new QSslSocket;
71#if QT_CONFIG(localserver)
72 else if (connection->d_func()->isLocalSocket)
73 socket = new QLocalSocket;
74#endif
75 else
76 socket = new QTcpSocket;
77#else
78 socket = new QTcpSocket;
79#endif
80#ifndef QT_NO_NETWORKPROXY
81 // Set by QNAM anyway, but let's be safe here
82 if (auto s = qobject_cast<QAbstractSocket *>(object: socket))
83 s->setProxy(QNetworkProxy::NoProxy);
84#endif
85
86 // After some back and forth in all the last years, this is now a DirectConnection because otherwise
87 // the state inside the *Socket classes gets messed up, also in conjunction with the socket notifiers
88 // which behave slightly differently on Windows vs Linux
89 QObject::connect(sender: socket, signal: &QIODevice::bytesWritten,
90 context: this, slot: &QHttpNetworkConnectionChannel::_q_bytesWritten,
91 type: Qt::DirectConnection);
92 QObject::connect(sender: socket, signal: &QIODevice::readyRead,
93 context: this, slot: &QHttpNetworkConnectionChannel::_q_readyRead,
94 type: Qt::DirectConnection);
95
96
97 QSocketAbstraction::visit(fn: [this](auto *socket){
98 using SocketType = std::remove_pointer_t<decltype(socket)>;
99 QObject::connect(socket, &SocketType::connected,
100 this, &QHttpNetworkConnectionChannel::_q_connected,
101 Qt::DirectConnection);
102
103 // The disconnected() and error() signals may already come
104 // while calling connectToHost().
105 // In case of a cached hostname or an IP this
106 // will then emit a signal to the user of QNetworkReply
107 // but cannot be caught because the user did not have a chance yet
108 // to connect to QNetworkReply's signals.
109 QObject::connect(socket, &SocketType::disconnected,
110 this, &QHttpNetworkConnectionChannel::_q_disconnected,
111 Qt::DirectConnection);
112 if constexpr (std::is_same_v<SocketType, QAbstractSocket>) {
113 QObject::connect(socket, &QAbstractSocket::errorOccurred,
114 this, &QHttpNetworkConnectionChannel::_q_error,
115 Qt::DirectConnection);
116#if QT_CONFIG(localserver)
117 } else if constexpr (std::is_same_v<SocketType, QLocalSocket>) {
118 auto convertAndForward = [this](QLocalSocket::LocalSocketError error) {
119 _q_error(static_cast<QAbstractSocket::SocketError>(error));
120 };
121 QObject::connect(socket, &SocketType::errorOccurred,
122 this, std::move(convertAndForward),
123 Qt::DirectConnection);
124#endif
125 }
126 }, socket);
127
128
129
130#ifndef QT_NO_NETWORKPROXY
131 if (auto *s = qobject_cast<QAbstractSocket *>(object: socket)) {
132 QObject::connect(sender: s, signal: &QAbstractSocket::proxyAuthenticationRequired,
133 context: this, slot: &QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired,
134 type: Qt::DirectConnection);
135 }
136#endif
137
138#ifndef QT_NO_SSL
139 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(object: socket);
140 if (sslSocket) {
141 // won't be a sslSocket if encrypt is false
142 QObject::connect(sender: sslSocket, signal: &QSslSocket::encrypted,
143 context: this, slot: &QHttpNetworkConnectionChannel::_q_encrypted,
144 type: Qt::DirectConnection);
145 QObject::connect(sender: sslSocket, signal: &QSslSocket::sslErrors,
146 context: this, slot: &QHttpNetworkConnectionChannel::_q_sslErrors,
147 type: Qt::DirectConnection);
148 QObject::connect(sender: sslSocket, signal: &QSslSocket::preSharedKeyAuthenticationRequired,
149 context: this, slot: &QHttpNetworkConnectionChannel::_q_preSharedKeyAuthenticationRequired,
150 type: Qt::DirectConnection);
151 QObject::connect(sender: sslSocket, signal: &QSslSocket::encryptedBytesWritten,
152 context: this, slot: &QHttpNetworkConnectionChannel::_q_encryptedBytesWritten,
153 type: Qt::DirectConnection);
154
155 if (ignoreAllSslErrors)
156 sslSocket->ignoreSslErrors();
157
158 if (!ignoreSslErrorsList.isEmpty())
159 sslSocket->ignoreSslErrors(errors: ignoreSslErrorsList);
160
161 if (sslConfiguration && !sslConfiguration->isNull())
162 sslSocket->setSslConfiguration(*sslConfiguration);
163 } else {
164#endif // !QT_NO_SSL
165 if (connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2)
166 protocolHandler.reset(p: new QHttpProtocolHandler(this));
167#ifndef QT_NO_SSL
168 }
169#endif
170
171#ifndef QT_NO_NETWORKPROXY
172 if (auto *s = qobject_cast<QAbstractSocket *>(object: socket);
173 s && proxy.type() != QNetworkProxy::NoProxy) {
174 s->setProxy(proxy);
175 }
176#endif
177 isInitialized = true;
178}
179
180
181void QHttpNetworkConnectionChannel::close()
182{
183 if (state == QHttpNetworkConnectionChannel::ClosingState)
184 return;
185
186 if (!socket)
187 state = QHttpNetworkConnectionChannel::IdleState;
188 else if (QSocketAbstraction::socketState(device: socket) == QAbstractSocket::UnconnectedState)
189 state = QHttpNetworkConnectionChannel::IdleState;
190 else
191 state = QHttpNetworkConnectionChannel::ClosingState;
192
193 // pendingEncrypt must only be true in between connected and encrypted states
194 pendingEncrypt = false;
195
196 if (socket) {
197 // socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
198 // there is no socket yet.
199 socket->close();
200 }
201}
202
203
204void QHttpNetworkConnectionChannel::abort()
205{
206 if (!socket)
207 state = QHttpNetworkConnectionChannel::IdleState;
208 else if (QSocketAbstraction::socketState(device: socket) == QAbstractSocket::UnconnectedState)
209 state = QHttpNetworkConnectionChannel::IdleState;
210 else
211 state = QHttpNetworkConnectionChannel::ClosingState;
212
213 // pendingEncrypt must only be true in between connected and encrypted states
214 pendingEncrypt = false;
215
216 if (socket) {
217 // socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
218 // there is no socket yet.
219 auto callAbort = [](auto *s) {
220 s->abort();
221 };
222 QSocketAbstraction::visit(fn&: callAbort, socket);
223 }
224}
225
226
227void QHttpNetworkConnectionChannel::sendRequest()
228{
229 Q_ASSERT(protocolHandler);
230 if (waitingForPotentialAbort) {
231 needInvokeSendRequest = true;
232 return;
233 }
234 protocolHandler->sendRequest();
235}
236
237/*
238 * Invoke "protocolHandler->sendRequest" using a queued connection.
239 * It's used to return to the event loop before invoking sendRequest when
240 * there's a very real chance that the request could have been aborted
241 * (i.e. after having emitted 'encrypted').
242 */
243void QHttpNetworkConnectionChannel::sendRequestDelayed()
244{
245 QMetaObject::invokeMethod(object: this, function: [this] {
246 if (reply)
247 sendRequest();
248 }, type: Qt::ConnectionType::QueuedConnection);
249}
250
251void QHttpNetworkConnectionChannel::_q_receiveReply()
252{
253 Q_ASSERT(protocolHandler);
254 if (waitingForPotentialAbort) {
255 needInvokeReceiveReply = true;
256 return;
257 }
258 protocolHandler->_q_receiveReply();
259}
260
261void QHttpNetworkConnectionChannel::_q_readyRead()
262{
263 Q_ASSERT(protocolHandler);
264 if (waitingForPotentialAbort) {
265 needInvokeReadyRead = true;
266 return;
267 }
268 protocolHandler->_q_readyRead();
269}
270
271// called when unexpectedly reading a -1 or when data is expected but socket is closed
272void QHttpNetworkConnectionChannel::handleUnexpectedEOF()
273{
274 Q_ASSERT(reply);
275 if (reconnectAttempts <= 0 || !request.methodIsIdempotent()) {
276 // too many errors reading/receiving/parsing the status, close the socket and emit error
277 requeueCurrentlyPipelinedRequests();
278 close();
279 reply->d_func()->errorString = connection->d_func()->errorDetail(errorCode: QNetworkReply::RemoteHostClosedError, socket);
280 emit reply->finishedWithError(errorCode: QNetworkReply::RemoteHostClosedError, detail: reply->d_func()->errorString);
281 reply = nullptr;
282 if (protocolHandler)
283 protocolHandler->setReply(nullptr);
284 request = QHttpNetworkRequest();
285 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
286 } else {
287 reconnectAttempts--;
288 reply->d_func()->clear();
289 reply->d_func()->connection = connection;
290 reply->d_func()->connectionChannel = this;
291 closeAndResendCurrentRequest();
292 }
293}
294
295bool QHttpNetworkConnectionChannel::ensureConnection()
296{
297 if (!isInitialized)
298 init();
299
300 QAbstractSocket::SocketState socketState = QSocketAbstraction::socketState(device: socket);
301
302 // resend this request after we receive the disconnected signal
303 // If !socket->isOpen() then we have already called close() on the socket, but there was still a
304 // pending connectToHost() for which we hadn't seen a connected() signal, yet. The connected()
305 // has now arrived (as indicated by socketState != ClosingState), but we cannot send anything on
306 // such a socket anymore.
307 if (socketState == QAbstractSocket::ClosingState ||
308 (socketState != QAbstractSocket::UnconnectedState && !socket->isOpen())) {
309 if (reply)
310 resendCurrent = true;
311 return false;
312 }
313
314 // already trying to connect?
315 if (socketState == QAbstractSocket::HostLookupState ||
316 socketState == QAbstractSocket::ConnectingState) {
317 return false;
318 }
319
320 // make sure that this socket is in a connected state, if not initiate
321 // connection to the host.
322 if (socketState != QAbstractSocket::ConnectedState) {
323 // connect to the host if not already connected.
324 state = QHttpNetworkConnectionChannel::ConnectingState;
325 pendingEncrypt = ssl;
326
327 // reset state
328 pipeliningSupported = PipeliningSupportUnknown;
329 authenticationCredentialsSent = false;
330 proxyCredentialsSent = false;
331 authenticator.detach();
332 QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(auth&: authenticator);
333 priv->hasFailed = false;
334 proxyAuthenticator.detach();
335 priv = QAuthenticatorPrivate::getPrivate(auth&: proxyAuthenticator);
336 priv->hasFailed = false;
337
338 // This workaround is needed since we use QAuthenticator for NTLM authentication. The "phase == Done"
339 // is the usual criteria for emitting authentication signals. The "phase" is set to "Done" when the
340 // last header for Authorization is generated by the QAuthenticator. Basic & Digest logic does not
341 // check the "phase" for generating the Authorization header. NTLM authentication is a two stage
342 // process & needs the "phase". To make sure the QAuthenticator uses the current username/password
343 // the phase is reset to Start.
344 priv = QAuthenticatorPrivate::getPrivate(auth&: authenticator);
345 if (priv && priv->phase == QAuthenticatorPrivate::Done)
346 priv->phase = QAuthenticatorPrivate::Start;
347 priv = QAuthenticatorPrivate::getPrivate(auth&: proxyAuthenticator);
348 if (priv && priv->phase == QAuthenticatorPrivate::Done)
349 priv->phase = QAuthenticatorPrivate::Start;
350
351 QString connectHost = connection->d_func()->hostName;
352 quint16 connectPort = connection->d_func()->port;
353
354 QHttpNetworkReply *potentialReply = connection->d_func()->predictNextRequestsReply();
355 if (potentialReply) {
356 QMetaObject::invokeMethod(obj: potentialReply, member: "socketStartedConnecting", c: Qt::QueuedConnection);
357 } else if (!h2RequestsToSend.isEmpty()) {
358 QMetaObject::invokeMethod(obj: std::as_const(t&: h2RequestsToSend).first().second, member: "socketStartedConnecting", c: Qt::QueuedConnection);
359 }
360
361#ifndef QT_NO_NETWORKPROXY
362 // HTTPS always use transparent proxy.
363 if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !ssl) {
364 connectHost = connection->d_func()->networkProxy.hostName();
365 connectPort = connection->d_func()->networkProxy.port();
366 }
367 if (auto *abSocket = qobject_cast<QAbstractSocket *>(object: socket);
368 abSocket && abSocket->proxy().type() == QNetworkProxy::HttpProxy) {
369 // Make user-agent field available to HTTP proxy socket engine (QTBUG-17223)
370 QByteArray value;
371 // ensureConnection is called before any request has been assigned, but can also be
372 // called again if reconnecting
373 if (request.url().isEmpty()) {
374 if (connection->connectionType()
375 == QHttpNetworkConnection::ConnectionTypeHTTP2Direct
376 || (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
377 && !h2RequestsToSend.isEmpty())) {
378 value = std::as_const(t&: h2RequestsToSend).first().first.headerField(name: "user-agent");
379 } else {
380 value = connection->d_func()->predictNextRequest().headerField(name: "user-agent");
381 }
382 } else {
383 value = request.headerField(name: "user-agent");
384 }
385 if (!value.isEmpty()) {
386 QNetworkProxy proxy(abSocket->proxy());
387 auto h = proxy.headers();
388 h.replaceOrAppend(name: QHttpHeaders::WellKnownHeader::UserAgent, newValue: value);
389 proxy.setHeaders(std::move(h));
390 abSocket->setProxy(proxy);
391 }
392 }
393#endif
394 if (ssl) {
395#ifndef QT_NO_SSL
396 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(object: socket);
397
398 // check whether we can re-use an existing SSL session
399 // (meaning another socket in this connection has already
400 // performed a full handshake)
401 if (auto ctx = connection->sslContext())
402 QSslSocketPrivate::checkSettingSslContext(sslSocket, std::move(ctx));
403
404 sslSocket->setPeerVerifyName(connection->d_func()->peerVerifyName);
405 sslSocket->connectToHostEncrypted(hostName: connectHost, port: connectPort, mode: QIODevice::ReadWrite, protocol: networkLayerPreference);
406 if (ignoreAllSslErrors)
407 sslSocket->ignoreSslErrors();
408 sslSocket->ignoreSslErrors(errors: ignoreSslErrorsList);
409
410 // limit the socket read buffer size. we will read everything into
411 // the QHttpNetworkReply anyway, so let's grow only that and not
412 // here and there.
413 sslSocket->setReadBufferSize(64*1024);
414#else
415 // Need to dequeue the request so that we can emit the error.
416 if (!reply)
417 connection->d_func()->dequeueRequest(socket);
418 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError);
419#endif
420 } else {
421 // In case of no proxy we can use the Unbuffered QTcpSocket
422#ifndef QT_NO_NETWORKPROXY
423 if (connection->d_func()->networkProxy.type() == QNetworkProxy::NoProxy
424 && connection->cacheProxy().type() == QNetworkProxy::NoProxy
425 && connection->transparentProxy().type() == QNetworkProxy::NoProxy) {
426#endif
427 if (auto *s = qobject_cast<QAbstractSocket *>(object: socket)) {
428 s->connectToHost(hostName: connectHost, port: connectPort,
429 mode: QIODevice::ReadWrite | QIODevice::Unbuffered,
430 protocol: networkLayerPreference);
431 // For an Unbuffered QTcpSocket, the read buffer size has a special meaning.
432 s->setReadBufferSize(1 * 1024);
433#if QT_CONFIG(localserver)
434 } else if (auto *s = qobject_cast<QLocalSocket *>(object: socket)) {
435 s->connectToServer(name: connectHost);
436#endif
437 }
438#ifndef QT_NO_NETWORKPROXY
439 } else {
440 auto *s = qobject_cast<QAbstractSocket *>(object: socket);
441 Q_ASSERT(s);
442 // limit the socket read buffer size. we will read everything into
443 // the QHttpNetworkReply anyway, so let's grow only that and not
444 // here and there.
445 s->connectToHost(hostName: connectHost, port: connectPort, mode: QIODevice::ReadWrite, protocol: networkLayerPreference);
446 s->setReadBufferSize(64 * 1024);
447 }
448#endif
449 }
450 return false;
451 }
452
453 // This code path for ConnectedState
454 if (pendingEncrypt) {
455 // Let's only be really connected when we have received the encrypted() signal. Else the state machine seems to mess up
456 // and corrupt the things sent to the server.
457 return false;
458 }
459
460 return true;
461}
462
463void QHttpNetworkConnectionChannel::allDone()
464{
465 Q_ASSERT(reply);
466
467 if (!reply) {
468 qWarning(msg: "QHttpNetworkConnectionChannel::allDone() called without reply. Please report at http://bugreports.qt.io/");
469 return;
470 }
471
472 // For clear text HTTP/2 we tried to upgrade from HTTP/1.1 to HTTP/2; for
473 // ConnectionTypeHTTP2Direct we can never be here in case of failure
474 // (after an attempt to read HTTP/1.1 as HTTP/2 frames) or we have a normal
475 // HTTP/2 response and thus can skip this test:
476 if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
477 && !ssl && !switchedToHttp2) {
478 if (Http2::is_protocol_upgraded(reply: *reply)) {
479 switchedToHttp2 = true;
480 protocolHandler->setReply(nullptr);
481
482 // As allDone() gets called from the protocol handler, it's not yet
483 // safe to delete it. There is no 'deleteLater', since
484 // QAbstractProtocolHandler is not a QObject. Instead delete it in
485 // a queued emission.
486
487 QMetaObject::invokeMethod(object: this, function: [oldHandler = std::move(protocolHandler)]() mutable {
488 oldHandler.reset();
489 }, type: Qt::QueuedConnection);
490
491 connection->fillHttp2Queue();
492 protocolHandler.reset(p: new QHttp2ProtocolHandler(this));
493 QHttp2ProtocolHandler *h2c = static_cast<QHttp2ProtocolHandler *>(protocolHandler.get());
494 QMetaObject::invokeMethod(obj: h2c, member: "_q_receiveReply", c: Qt::QueuedConnection);
495 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
496 return;
497 } else {
498 // Ok, whatever happened, we do not try HTTP/2 anymore ...
499 connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP);
500 connection->d_func()->activeChannelCount = connection->d_func()->channelCount;
501 }
502 }
503
504 // while handling 401 & 407, we might reset the status code, so save this.
505 bool emitFinished = reply->d_func()->shouldEmitSignals();
506 bool connectionCloseEnabled = reply->d_func()->isConnectionCloseEnabled();
507 detectPipeliningSupport();
508
509 handleStatus();
510 // handleStatus() might have removed the reply because it already called connection->emitReplyError()
511
512 // queue the finished signal, this is required since we might send new requests from
513 // slot connected to it. The socket will not fire readyRead signal, if we are already
514 // in the slot connected to readyRead
515 if (reply && emitFinished)
516 QMetaObject::invokeMethod(obj: reply, member: "finished", c: Qt::QueuedConnection);
517
518
519 // reset the reconnection attempts after we receive a complete reply.
520 // in case of failures, each channel will attempt two reconnects before emitting error.
521 reconnectAttempts = reconnectAttemptsDefault;
522
523 // now the channel can be seen as free/idle again, all signal emissions for the reply have been done
524 if (state != QHttpNetworkConnectionChannel::ClosingState)
525 state = QHttpNetworkConnectionChannel::IdleState;
526
527 // if it does not need to be sent again we can set it to 0
528 // the previous code did not do that and we had problems with accidental re-sending of a
529 // finished request.
530 // Note that this may trigger a segfault at some other point. But then we can fix the underlying
531 // problem.
532 if (!resendCurrent) {
533 request = QHttpNetworkRequest();
534 reply = nullptr;
535 protocolHandler->setReply(nullptr);
536 }
537
538 // move next from pipeline to current request
539 if (!alreadyPipelinedRequests.isEmpty()) {
540 if (resendCurrent || connectionCloseEnabled || QSocketAbstraction::socketState(device: socket) != QAbstractSocket::ConnectedState) {
541 // move the pipelined ones back to the main queue
542 requeueCurrentlyPipelinedRequests();
543 close();
544 } else {
545 // there were requests pipelined in and we can continue
546 HttpMessagePair messagePair = alreadyPipelinedRequests.takeFirst();
547
548 request = messagePair.first;
549 reply = messagePair.second;
550 protocolHandler->setReply(messagePair.second);
551 state = QHttpNetworkConnectionChannel::ReadingState;
552 resendCurrent = false;
553
554 written = 0; // message body, excluding the header, irrelevant here
555 bytesTotal = 0; // message body total, excluding the header, irrelevant here
556
557 // pipeline even more
558 connection->d_func()->fillPipeline(socket);
559
560 // continue reading
561 //_q_receiveReply();
562 // this was wrong, allDone gets called from that function anyway.
563 }
564 } else if (alreadyPipelinedRequests.isEmpty() && socket->bytesAvailable() > 0) {
565 // this is weird. we had nothing pipelined but still bytes available. better close it.
566 close();
567
568 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
569 } else if (alreadyPipelinedRequests.isEmpty()) {
570 if (connectionCloseEnabled)
571 if (QSocketAbstraction::socketState(device: socket) != QAbstractSocket::UnconnectedState)
572 close();
573 if (qobject_cast<QHttpNetworkConnection*>(object: connection))
574 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
575 }
576}
577
578void QHttpNetworkConnectionChannel::detectPipeliningSupport()
579{
580 Q_ASSERT(reply);
581 // detect HTTP Pipelining support
582 QByteArray serverHeaderField;
583 if (
584 // check for HTTP/1.1
585 (reply->majorVersion() == 1 && reply->minorVersion() == 1)
586 // check for not having connection close
587 && (!reply->d_func()->isConnectionCloseEnabled())
588 // check if it is still connected
589 && (QSocketAbstraction::socketState(device: socket) == QAbstractSocket::ConnectedState)
590 // check for broken servers in server reply header
591 // this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining
592 && (serverHeaderField = reply->headerField(name: "Server"), !serverHeaderField.contains(bv: "Microsoft-IIS/4."))
593 && (!serverHeaderField.contains(bv: "Microsoft-IIS/5."))
594 && (!serverHeaderField.contains(bv: "Netscape-Enterprise/3."))
595 // this is adpoted from the knowledge of the Nokia 7.x browser team (DEF143319)
596 && (!serverHeaderField.contains(bv: "WebLogic"))
597 && (!serverHeaderField.startsWith(bv: "Rocket")) // a Python Web Server, see Web2py.com
598 ) {
599 pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningProbablySupported;
600 } else {
601 pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
602 }
603}
604
605// called when the connection broke and we need to queue some pipelined requests again
606void QHttpNetworkConnectionChannel::requeueCurrentlyPipelinedRequests()
607{
608 for (int i = 0; i < alreadyPipelinedRequests.size(); i++)
609 connection->d_func()->requeueRequest(pair: alreadyPipelinedRequests.at(i));
610 alreadyPipelinedRequests.clear();
611
612 // only run when the QHttpNetworkConnection is not currently being destructed, e.g.
613 // this function is called from _q_disconnected which is called because
614 // of ~QHttpNetworkConnectionPrivate
615 if (qobject_cast<QHttpNetworkConnection*>(object: connection))
616 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
617}
618
619void QHttpNetworkConnectionChannel::handleStatus()
620{
621 Q_ASSERT(socket);
622 Q_ASSERT(reply);
623
624 int statusCode = reply->statusCode();
625 bool resend = false;
626
627 switch (statusCode) {
628 case 301:
629 case 302:
630 case 303:
631 case 305:
632 case 307:
633 case 308: {
634 // Parse the response headers and get the "location" url
635 QUrl redirectUrl = connection->d_func()->parseRedirectResponse(socket, reply);
636 if (redirectUrl.isValid())
637 reply->setRedirectUrl(redirectUrl);
638
639 if ((statusCode == 307 || statusCode == 308) && !resetUploadData()) {
640 // Couldn't reset the upload data, which means it will be unable to POST the data -
641 // this would lead to a long wait until it eventually failed and then retried.
642 // Instead of doing that we fail here instead, resetUploadData will already have emitted
643 // a ContentReSendError, so we're done.
644 } else if (qobject_cast<QHttpNetworkConnection *>(object: connection)) {
645 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
646 }
647 break;
648 }
649 case 401: // auth required
650 case 407: // proxy auth required
651 if (connection->d_func()->handleAuthenticateChallenge(socket, reply, isProxy: (statusCode == 407), resend)) {
652 if (resend) {
653 if (!resetUploadData())
654 break;
655
656 reply->d_func()->eraseData();
657
658 if (alreadyPipelinedRequests.isEmpty()) {
659 // this does a re-send without closing the connection
660 resendCurrent = true;
661 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
662 } else {
663 // we had requests pipelined.. better close the connection in closeAndResendCurrentRequest
664 closeAndResendCurrentRequest();
665 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
666 }
667 } else {
668 //authentication cancelled, close the channel.
669 close();
670 }
671 } else {
672 emit reply->headerChanged();
673 emit reply->readyRead();
674 QNetworkReply::NetworkError errorCode = (statusCode == 407)
675 ? QNetworkReply::ProxyAuthenticationRequiredError
676 : QNetworkReply::AuthenticationRequiredError;
677 reply->d_func()->errorString = connection->d_func()->errorDetail(errorCode, socket);
678 emit reply->finishedWithError(errorCode, detail: reply->d_func()->errorString);
679 }
680 break;
681 default:
682 if (qobject_cast<QHttpNetworkConnection*>(object: connection))
683 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
684 }
685}
686
687bool QHttpNetworkConnectionChannel::resetUploadData()
688{
689 if (!reply) {
690 //this happens if server closes connection while QHttpNetworkConnectionPrivate::_q_startNextRequest is pending
691 return false;
692 }
693 if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct
694 || switchedToHttp2) {
695 // The else branch doesn't make any sense for HTTP/2, since 1 channel is multiplexed into
696 // many streams. And having one stream fail to reset upload data should not completely close
697 // the channel. Handled in the http2 protocol handler.
698 } else if (QNonContiguousByteDevice *uploadByteDevice = request.uploadByteDevice()) {
699 if (!uploadByteDevice->reset()) {
700 connection->d_func()->emitReplyError(socket, reply, errorCode: QNetworkReply::ContentReSendError);
701 return false;
702 }
703 written = 0;
704 }
705 return true;
706}
707
708#ifndef QT_NO_NETWORKPROXY
709
710void QHttpNetworkConnectionChannel::setProxy(const QNetworkProxy &networkProxy)
711{
712 if (auto *s = qobject_cast<QAbstractSocket *>(object: socket))
713 s->setProxy(networkProxy);
714
715 proxy = networkProxy;
716}
717
718#endif
719
720#ifndef QT_NO_SSL
721
722void QHttpNetworkConnectionChannel::ignoreSslErrors()
723{
724 if (socket)
725 static_cast<QSslSocket *>(socket)->ignoreSslErrors();
726
727 ignoreAllSslErrors = true;
728}
729
730
731void QHttpNetworkConnectionChannel::ignoreSslErrors(const QList<QSslError> &errors)
732{
733 if (socket)
734 static_cast<QSslSocket *>(socket)->ignoreSslErrors(errors);
735
736 ignoreSslErrorsList = errors;
737}
738
739void QHttpNetworkConnectionChannel::setSslConfiguration(const QSslConfiguration &config)
740{
741 if (socket)
742 static_cast<QSslSocket *>(socket)->setSslConfiguration(config);
743
744 if (sslConfiguration)
745 *sslConfiguration = config;
746 else
747 sslConfiguration = QSslConfiguration(config);
748}
749
750#endif
751
752void QHttpNetworkConnectionChannel::pipelineInto(HttpMessagePair &pair)
753{
754 // this is only called for simple GET
755
756 QHttpNetworkRequest &request = pair.first;
757 QHttpNetworkReply *reply = pair.second;
758 reply->d_func()->clear();
759 reply->d_func()->connection = connection;
760 reply->d_func()->connectionChannel = this;
761 reply->d_func()->autoDecompress = request.d->autoDecompress;
762 reply->d_func()->pipeliningUsed = true;
763
764#ifndef QT_NO_NETWORKPROXY
765 pipeline.append(a: QHttpNetworkRequestPrivate::header(request,
766 throughProxy: (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy)));
767#else
768 pipeline.append(QHttpNetworkRequestPrivate::header(request, false));
769#endif
770
771 alreadyPipelinedRequests.append(t: pair);
772
773 // pipelineFlush() needs to be called at some point afterwards
774}
775
776void QHttpNetworkConnectionChannel::pipelineFlush()
777{
778 if (pipeline.isEmpty())
779 return;
780
781 // The goal of this is so that we have everything in one TCP packet.
782 // For the Unbuffered QTcpSocket this is manually needed, the buffered
783 // QTcpSocket does it automatically.
784 // Also, sometimes the OS does it for us (Nagle's algorithm) but that
785 // happens only sometimes.
786 socket->write(data: pipeline);
787 pipeline.clear();
788}
789
790
791void QHttpNetworkConnectionChannel::closeAndResendCurrentRequest()
792{
793 requeueCurrentlyPipelinedRequests();
794 close();
795 if (reply)
796 resendCurrent = true;
797 if (qobject_cast<QHttpNetworkConnection*>(object: connection))
798 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
799}
800
801void QHttpNetworkConnectionChannel::resendCurrentRequest()
802{
803 requeueCurrentlyPipelinedRequests();
804 if (reply)
805 resendCurrent = true;
806 if (qobject_cast<QHttpNetworkConnection*>(object: connection))
807 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
808}
809
810bool QHttpNetworkConnectionChannel::isSocketBusy() const
811{
812 return (state & QHttpNetworkConnectionChannel::BusyState);
813}
814
815bool QHttpNetworkConnectionChannel::isSocketWriting() const
816{
817 return (state & QHttpNetworkConnectionChannel::WritingState);
818}
819
820bool QHttpNetworkConnectionChannel::isSocketWaiting() const
821{
822 return (state & QHttpNetworkConnectionChannel::WaitingState);
823}
824
825bool QHttpNetworkConnectionChannel::isSocketReading() const
826{
827 return (state & QHttpNetworkConnectionChannel::ReadingState);
828}
829
830void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes)
831{
832 Q_UNUSED(bytes);
833 if (ssl) {
834 // In the SSL case we want to send data from encryptedBytesWritten signal since that one
835 // is the one going down to the actual network, not only into some SSL buffer.
836 return;
837 }
838
839 // bytes have been written to the socket. write even more of them :)
840 if (isSocketWriting())
841 sendRequest();
842 // otherwise we do nothing
843}
844
845void QHttpNetworkConnectionChannel::_q_disconnected()
846{
847 if (state == QHttpNetworkConnectionChannel::ClosingState) {
848 state = QHttpNetworkConnectionChannel::IdleState;
849 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
850 return;
851 }
852
853 // read the available data before closing (also done in _q_error for other codepaths)
854 if ((isSocketWaiting() || isSocketReading()) && socket->bytesAvailable()) {
855 if (reply) {
856 state = QHttpNetworkConnectionChannel::ReadingState;
857 _q_receiveReply();
858 }
859 } else if (state == QHttpNetworkConnectionChannel::IdleState && resendCurrent) {
860 // re-sending request because the socket was in ClosingState
861 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
862 }
863 state = QHttpNetworkConnectionChannel::IdleState;
864 if (alreadyPipelinedRequests.size()) {
865 // If nothing was in a pipeline, no need in calling
866 // _q_startNextRequest (which it does):
867 requeueCurrentlyPipelinedRequests();
868 }
869
870 pendingEncrypt = false;
871}
872
873
874void QHttpNetworkConnectionChannel::_q_connected_abstract_socket(QAbstractSocket *absSocket)
875{
876 // For the Happy Eyeballs we need to check if this is the first channel to connect.
877 if (connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::HostLookupPending || connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv4or6) {
878 if (connection->d_func()->delayedConnectionTimer.isActive())
879 connection->d_func()->delayedConnectionTimer.stop();
880 if (networkLayerPreference == QAbstractSocket::IPv4Protocol)
881 connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
882 else if (networkLayerPreference == QAbstractSocket::IPv6Protocol)
883 connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
884 else {
885 if (absSocket->peerAddress().protocol() == QAbstractSocket::IPv4Protocol)
886 connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
887 else
888 connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
889 }
890 connection->d_func()->networkLayerDetected(protocol: networkLayerPreference);
891 if (connection->d_func()->activeChannelCount > 1 && !connection->d_func()->encrypt)
892 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
893 } else {
894 bool anyProtocol = networkLayerPreference == QAbstractSocket::AnyIPProtocol;
895 if (((connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv4)
896 && (networkLayerPreference != QAbstractSocket::IPv4Protocol && !anyProtocol))
897 || ((connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv6)
898 && (networkLayerPreference != QAbstractSocket::IPv6Protocol && !anyProtocol))) {
899 close();
900 // This is the second connection so it has to be closed and we can schedule it for another request.
901 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
902 return;
903 }
904 //The connections networkLayerState had already been decided.
905 }
906
907 // improve performance since we get the request sent by the kernel ASAP
908 //absSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
909 // We have this commented out now. It did not have the effect we wanted. If we want to
910 // do this properly, Qt has to combine multiple HTTP requests into one buffer
911 // and send this to the kernel in one syscall and then the kernel immediately sends
912 // it as one TCP packet because of TCP_NODELAY.
913 // However, this code is currently not in Qt, so we rely on the kernel combining
914 // the requests into one TCP packet.
915
916 // not sure yet if it helps, but it makes sense
917 absSocket->setSocketOption(option: QAbstractSocket::KeepAliveOption, value: 1);
918
919 pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
920
921 if (QNetworkConnectionMonitor::isEnabled()) {
922 auto connectionPrivate = connection->d_func();
923 if (!connectionPrivate->connectionMonitor.isMonitoring()) {
924 // Now that we have a pair of addresses, we can start monitoring the
925 // connection status to handle its loss properly.
926 if (connectionPrivate->connectionMonitor.setTargets(local: absSocket->localAddress(), remote: absSocket->peerAddress()))
927 connectionPrivate->connectionMonitor.startMonitoring();
928 }
929 }
930
931 // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
932 //channels[i].reconnectAttempts = 2;
933 if (ssl || pendingEncrypt) { // FIXME: Didn't work properly with pendingEncrypt only, we should refactor this into an EncrypingState
934#ifndef QT_NO_SSL
935 if (!connection->sslContext()) {
936 // this socket is making the 1st handshake for this connection,
937 // we need to set the SSL context so new sockets can reuse it
938 if (auto socketSslContext = QSslSocketPrivate::sslContext(socket: static_cast<QSslSocket*>(absSocket)))
939 connection->setSslContext(std::move(socketSslContext));
940 }
941#endif
942 } else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
943 state = QHttpNetworkConnectionChannel::IdleState;
944 protocolHandler.reset(p: new QHttp2ProtocolHandler(this));
945 if (h2RequestsToSend.size() > 0) {
946 // In case our peer has sent us its settings (window size, max concurrent streams etc.)
947 // let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection).
948 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
949 }
950 } else {
951 state = QHttpNetworkConnectionChannel::IdleState;
952 const bool tryProtocolUpgrade = connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2;
953 if (tryProtocolUpgrade) {
954 // For HTTP/1.1 it's already created and never reset.
955 protocolHandler.reset(p: new QHttpProtocolHandler(this));
956 }
957 switchedToHttp2 = false;
958
959 if (!reply)
960 connection->d_func()->dequeueRequest(socket: absSocket);
961
962 if (reply) {
963 if (tryProtocolUpgrade) {
964 // Let's augment our request with some magic headers and try to
965 // switch to HTTP/2.
966 Http2::appendProtocolUpgradeHeaders(configuration: connection->http2Parameters(), request: &request);
967 }
968 sendRequest();
969 }
970 }
971}
972
973#if QT_CONFIG(localserver)
974void QHttpNetworkConnectionChannel::_q_connected_local_socket(QLocalSocket *localSocket)
975{
976 state = QHttpNetworkConnectionChannel::IdleState;
977 if (!reply) // No reply object, try to dequeue a request (which is paired with a reply):
978 connection->d_func()->dequeueRequest(socket: localSocket);
979 if (reply)
980 sendRequest();
981}
982#endif
983
984void QHttpNetworkConnectionChannel::_q_connected()
985{
986 if (auto *s = qobject_cast<QAbstractSocket *>(object: socket))
987 _q_connected_abstract_socket(absSocket: s);
988#if QT_CONFIG(localserver)
989 else if (auto *s = qobject_cast<QLocalSocket *>(object: socket))
990 _q_connected_local_socket(localSocket: s);
991#endif
992}
993
994void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socketError)
995{
996 if (!socket)
997 return;
998 QNetworkReply::NetworkError errorCode = QNetworkReply::UnknownNetworkError;
999
1000 switch (socketError) {
1001 case QAbstractSocket::HostNotFoundError:
1002 errorCode = QNetworkReply::HostNotFoundError;
1003 break;
1004 case QAbstractSocket::ConnectionRefusedError:
1005 errorCode = QNetworkReply::ConnectionRefusedError;
1006#ifndef QT_NO_NETWORKPROXY
1007 if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !ssl)
1008 errorCode = QNetworkReply::ProxyConnectionRefusedError;
1009#endif
1010 break;
1011 case QAbstractSocket::RemoteHostClosedError:
1012 // This error for SSL comes twice in a row, first from SSL layer ("The TLS/SSL connection has been closed") then from TCP layer.
1013 // Depending on timing it can also come three times in a row (first time when we try to write into a closing QSslSocket).
1014 // The reconnectAttempts handling catches the cases where we can re-send the request.
1015 if (!reply && state == QHttpNetworkConnectionChannel::IdleState) {
1016 // Not actually an error, it is normal for Keep-Alive connections to close after some time if no request
1017 // is sent on them. No need to error the other replies below. Just bail out here.
1018 // The _q_disconnected will handle the possibly pipelined replies. HTTP/2 is special for now,
1019 // we do not resend, but must report errors if any request is in progress (note, while
1020 // not in its sendRequest(), protocol handler switches the channel to IdleState, thus
1021 // this check is under this condition in 'if'):
1022 if (protocolHandler) {
1023 if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct
1024 || (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
1025 && switchedToHttp2)) {
1026 auto h2Handler = static_cast<QHttp2ProtocolHandler *>(protocolHandler.get());
1027 h2Handler->handleConnectionClosure();
1028 }
1029 }
1030 return;
1031 } else if (state != QHttpNetworkConnectionChannel::IdleState && state != QHttpNetworkConnectionChannel::ReadingState) {
1032 // Try to reconnect/resend before sending an error.
1033 // While "Reading" the _q_disconnected() will handle this.
1034 // If we're using ssl then the protocolHandler is not initialized until
1035 // "encrypted" has been emitted, since retrying requires the protocolHandler (asserted)
1036 // we will not try if encryption is not done.
1037 if (!pendingEncrypt && reconnectAttempts-- > 0) {
1038 resendCurrentRequest();
1039 return;
1040 } else {
1041 errorCode = QNetworkReply::RemoteHostClosedError;
1042 }
1043 } else if (state == QHttpNetworkConnectionChannel::ReadingState) {
1044 if (!reply)
1045 break;
1046
1047 if (!reply->d_func()->expectContent()) {
1048 // No content expected, this is a valid way to have the connection closed by the server
1049 // We need to invoke this asynchronously to make sure the state() of the socket is on QAbstractSocket::UnconnectedState
1050 QMetaObject::invokeMethod(obj: this, member: "_q_receiveReply", c: Qt::QueuedConnection);
1051 return;
1052 }
1053 if (reply->contentLength() == -1 && !reply->d_func()->isChunked()) {
1054 // There was no content-length header and it's not chunked encoding,
1055 // so this is a valid way to have the connection closed by the server
1056 // We need to invoke this asynchronously to make sure the state() of the socket is on QAbstractSocket::UnconnectedState
1057 QMetaObject::invokeMethod(obj: this, member: "_q_receiveReply", c: Qt::QueuedConnection);
1058 return;
1059 }
1060 // ok, we got a disconnect even though we did not expect it
1061 // Try to read everything from the socket before we emit the error.
1062 if (socket->bytesAvailable()) {
1063 // Read everything from the socket into the reply buffer.
1064 // we can ignore the readbuffersize as the data is already
1065 // in memory and we will not receive more data on the socket.
1066 reply->setReadBufferSize(0);
1067 reply->setDownstreamLimited(false);
1068 _q_receiveReply();
1069 if (!reply) {
1070 // No more reply assigned after the previous call? Then it had been finished successfully.
1071 requeueCurrentlyPipelinedRequests();
1072 state = QHttpNetworkConnectionChannel::IdleState;
1073 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
1074 return;
1075 }
1076 }
1077
1078 errorCode = QNetworkReply::RemoteHostClosedError;
1079 } else {
1080 errorCode = QNetworkReply::RemoteHostClosedError;
1081 }
1082 break;
1083 case QAbstractSocket::SocketTimeoutError:
1084 // try to reconnect/resend before sending an error.
1085 if (state == QHttpNetworkConnectionChannel::WritingState && (reconnectAttempts-- > 0)) {
1086 resendCurrentRequest();
1087 return;
1088 }
1089 errorCode = QNetworkReply::TimeoutError;
1090 break;
1091 case QAbstractSocket::ProxyConnectionRefusedError:
1092 errorCode = QNetworkReply::ProxyConnectionRefusedError;
1093 break;
1094 case QAbstractSocket::ProxyAuthenticationRequiredError:
1095 errorCode = QNetworkReply::ProxyAuthenticationRequiredError;
1096 break;
1097 case QAbstractSocket::SslHandshakeFailedError:
1098 errorCode = QNetworkReply::SslHandshakeFailedError;
1099 break;
1100 case QAbstractSocket::ProxyConnectionClosedError:
1101 // try to reconnect/resend before sending an error.
1102 if (reconnectAttempts-- > 0) {
1103 resendCurrentRequest();
1104 return;
1105 }
1106 errorCode = QNetworkReply::ProxyConnectionClosedError;
1107 break;
1108 case QAbstractSocket::ProxyConnectionTimeoutError:
1109 // try to reconnect/resend before sending an error.
1110 if (reconnectAttempts-- > 0) {
1111 resendCurrentRequest();
1112 return;
1113 }
1114 errorCode = QNetworkReply::ProxyTimeoutError;
1115 break;
1116 default:
1117 // all other errors are treated as NetworkError
1118 errorCode = QNetworkReply::UnknownNetworkError;
1119 break;
1120 }
1121 QPointer<QHttpNetworkConnection> that = connection;
1122 QString errorString = connection->d_func()->errorDetail(errorCode, socket, extraDetail: socket->errorString());
1123
1124 // In the HostLookupPending state the channel should not emit the error.
1125 // This will instead be handled by the connection.
1126 if (!connection->d_func()->shouldEmitChannelError(socket))
1127 return;
1128
1129 // emit error for all waiting replies
1130 do {
1131 // First requeue the already pipelined requests for the current failed reply,
1132 // then dequeue pending requests so we can also mark them as finished with error
1133 if (reply)
1134 requeueCurrentlyPipelinedRequests();
1135 else
1136 connection->d_func()->dequeueRequest(socket);
1137
1138 if (reply) {
1139 reply->d_func()->errorString = errorString;
1140 reply->d_func()->httpErrorCode = errorCode;
1141 emit reply->finishedWithError(errorCode, detail: errorString);
1142 reply = nullptr;
1143 if (protocolHandler)
1144 protocolHandler->setReply(nullptr);
1145 }
1146 } while (!connection->d_func()->highPriorityQueue.isEmpty()
1147 || !connection->d_func()->lowPriorityQueue.isEmpty());
1148
1149 if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
1150 || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
1151 const auto h2RequestsToSendCopy = std::exchange(obj&: h2RequestsToSend, new_val: {});
1152 for (const auto &httpMessagePair : h2RequestsToSendCopy) {
1153 // emit error for all replies
1154 QHttpNetworkReply *currentReply = httpMessagePair.second;
1155 currentReply->d_func()->errorString = errorString;
1156 currentReply->d_func()->httpErrorCode = errorCode;
1157 Q_ASSERT(currentReply);
1158 emit currentReply->finishedWithError(errorCode, detail: errorString);
1159 }
1160 }
1161
1162 // send the next request
1163 QMetaObject::invokeMethod(obj: that, member: "_q_startNextRequest", c: Qt::QueuedConnection);
1164
1165 if (that) {
1166 //signal emission triggered event loop
1167 if (!socket)
1168 state = QHttpNetworkConnectionChannel::IdleState;
1169 else if (QSocketAbstraction::socketState(device: socket) == QAbstractSocket::UnconnectedState)
1170 state = QHttpNetworkConnectionChannel::IdleState;
1171 else
1172 state = QHttpNetworkConnectionChannel::ClosingState;
1173
1174 // pendingEncrypt must only be true in between connected and encrypted states
1175 pendingEncrypt = false;
1176 }
1177}
1178
1179#ifndef QT_NO_NETWORKPROXY
1180void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth)
1181{
1182 if ((connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
1183 && (switchedToHttp2 || h2RequestsToSend.size() > 0))
1184 || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
1185 if (h2RequestsToSend.size() > 0)
1186 connection->d_func()->emitProxyAuthenticationRequired(chan: this, proxy, auth);
1187 } else { // HTTP
1188 // Need to dequeue the request before we can emit the error.
1189 if (!reply)
1190 connection->d_func()->dequeueRequest(socket);
1191 if (reply)
1192 connection->d_func()->emitProxyAuthenticationRequired(chan: this, proxy, auth);
1193 }
1194}
1195#endif
1196
1197void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead()
1198{
1199 if (reply)
1200 sendRequest();
1201}
1202
1203void QHttpNetworkConnectionChannel::emitFinishedWithError(QNetworkReply::NetworkError error,
1204 const char *message)
1205{
1206 if (reply)
1207 emit reply->finishedWithError(errorCode: error, detail: QHttpNetworkConnectionChannel::tr(s: message));
1208 const auto h2RequestsToSendCopy = h2RequestsToSend;
1209 for (const auto &httpMessagePair : h2RequestsToSendCopy) {
1210 QHttpNetworkReply *currentReply = httpMessagePair.second;
1211 Q_ASSERT(currentReply);
1212 emit currentReply->finishedWithError(errorCode: error, detail: QHttpNetworkConnectionChannel::tr(s: message));
1213 }
1214}
1215
1216#ifndef QT_NO_SSL
1217void QHttpNetworkConnectionChannel::_q_encrypted()
1218{
1219 QSslSocket *sslSocket = qobject_cast<QSslSocket *>(object: socket);
1220 Q_ASSERT(sslSocket);
1221
1222 if (!protocolHandler && connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
1223 // ConnectionTypeHTTP2Direct does not rely on ALPN/NPN to negotiate HTTP/2,
1224 // after establishing a secure connection we immediately start sending
1225 // HTTP/2 frames.
1226 switch (sslSocket->sslConfiguration().nextProtocolNegotiationStatus()) {
1227 case QSslConfiguration::NextProtocolNegotiationNegotiated: {
1228 QByteArray nextProtocol = sslSocket->sslConfiguration().nextNegotiatedProtocol();
1229 if (nextProtocol == QSslConfiguration::NextProtocolHttp1_1) {
1230 // fall through to create a QHttpProtocolHandler
1231 } else if (nextProtocol == QSslConfiguration::ALPNProtocolHTTP2) {
1232 switchedToHttp2 = true;
1233 protocolHandler.reset(p: new QHttp2ProtocolHandler(this));
1234 connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP2);
1235 break;
1236 } else {
1237 emitFinishedWithError(error: QNetworkReply::SslHandshakeFailedError,
1238 message: "detected unknown Next Protocol Negotiation protocol");
1239 break;
1240 }
1241 }
1242 Q_FALLTHROUGH();
1243 case QSslConfiguration::NextProtocolNegotiationUnsupported: // No agreement, try HTTP/1(.1)
1244 case QSslConfiguration::NextProtocolNegotiationNone: {
1245 protocolHandler.reset(p: new QHttpProtocolHandler(this));
1246
1247 QSslConfiguration newConfiguration = sslSocket->sslConfiguration();
1248 QList<QByteArray> protocols = newConfiguration.allowedNextProtocols();
1249 const int nProtocols = protocols.size();
1250 // Clear the protocol that we failed to negotiate, so we do not try
1251 // it again on other channels that our connection can create/open.
1252 if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2)
1253 protocols.removeAll(t: QSslConfiguration::ALPNProtocolHTTP2);
1254
1255 if (nProtocols > protocols.size()) {
1256 newConfiguration.setAllowedNextProtocols(protocols);
1257 const int channelCount = connection->d_func()->channelCount;
1258 for (int i = 0; i < channelCount; ++i)
1259 connection->d_func()->channels[i].setSslConfiguration(newConfiguration);
1260 }
1261
1262 connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP);
1263 // We use only one channel for HTTP/2, but normally six for
1264 // HTTP/1.1 - let's restore this number to the reserved number of
1265 // channels:
1266 if (connection->d_func()->activeChannelCount < connection->d_func()->channelCount) {
1267 connection->d_func()->activeChannelCount = connection->d_func()->channelCount;
1268 // re-queue requests from HTTP/2 queue to HTTP queue, if any
1269 requeueHttp2Requests();
1270 }
1271 break;
1272 }
1273 default:
1274 emitFinishedWithError(error: QNetworkReply::SslHandshakeFailedError,
1275 message: "detected unknown Next Protocol Negotiation protocol");
1276 }
1277 } else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
1278 || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
1279 // We have to reset QHttp2ProtocolHandler's state machine, it's a new
1280 // connection and the handler's state is unique per connection.
1281 protocolHandler.reset(p: new QHttp2ProtocolHandler(this));
1282 }
1283
1284 if (!socket)
1285 return; // ### error
1286 state = QHttpNetworkConnectionChannel::IdleState;
1287 pendingEncrypt = false;
1288
1289 if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2 ||
1290 connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
1291 if (!h2RequestsToSend.isEmpty()) {
1292 // Similar to HTTP/1.1 counterpart below:
1293 const auto &pair = std::as_const(t&: h2RequestsToSend).first();
1294 waitingForPotentialAbort = true;
1295 emit pair.second->encrypted();
1296
1297 // We don't send or handle any received data until any effects from
1298 // emitting encrypted() have been processed. This is necessary
1299 // because the user may have called abort(). We may also abort the
1300 // whole connection if the request has been aborted and there is
1301 // no more requests to send.
1302 QMetaObject::invokeMethod(object: this,
1303 function: &QHttpNetworkConnectionChannel::checkAndResumeCommunication,
1304 type: Qt::QueuedConnection);
1305
1306 // In case our peer has sent us its settings (window size, max concurrent streams etc.)
1307 // let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection).
1308 }
1309 } else { // HTTP
1310 if (!reply)
1311 connection->d_func()->dequeueRequest(socket);
1312 if (reply) {
1313 reply->setHttp2WasUsed(false);
1314 Q_ASSERT(reply->d_func()->connectionChannel == this);
1315 emit reply->encrypted();
1316 }
1317 if (reply)
1318 sendRequestDelayed();
1319 }
1320 QMetaObject::invokeMethod(obj: connection, member: "_q_startNextRequest", c: Qt::QueuedConnection);
1321}
1322
1323
1324void QHttpNetworkConnectionChannel::checkAndResumeCommunication()
1325{
1326 Q_ASSERT(connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
1327 || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct);
1328
1329 // Because HTTP/2 requires that we send a SETTINGS frame as the first thing we do, and respond
1330 // to a SETTINGS frame with an ACK, we need to delay any handling until we can ensure that any
1331 // effects from emitting encrypted() have been processed.
1332 // This function is called after encrypted() was emitted, so check for changes.
1333
1334 if (!reply && h2RequestsToSend.isEmpty())
1335 abort();
1336 waitingForPotentialAbort = false;
1337 if (needInvokeReadyRead)
1338 _q_readyRead();
1339 if (needInvokeReceiveReply)
1340 _q_receiveReply();
1341 if (needInvokeSendRequest)
1342 sendRequest();
1343}
1344
1345void QHttpNetworkConnectionChannel::requeueHttp2Requests()
1346{
1347 const auto h2RequestsToSendCopy = std::exchange(obj&: h2RequestsToSend, new_val: {});
1348 for (const auto &httpMessagePair : h2RequestsToSendCopy)
1349 connection->d_func()->requeueRequest(pair: httpMessagePair);
1350}
1351
1352void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
1353{
1354 if (!socket)
1355 return;
1356 //QNetworkReply::NetworkError errorCode = QNetworkReply::ProtocolFailure;
1357 // Also pause the connection because socket notifiers may fire while an user
1358 // dialog is displaying
1359 connection->d_func()->pauseConnection();
1360 if (pendingEncrypt && !reply)
1361 connection->d_func()->dequeueRequest(socket);
1362 if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP) {
1363 if (reply)
1364 emit reply->sslErrors(errors);
1365 }
1366#ifndef QT_NO_SSL
1367 else { // HTTP/2
1368 const auto h2RequestsToSendCopy = h2RequestsToSend;
1369 for (const auto &httpMessagePair : h2RequestsToSendCopy) {
1370 // emit SSL errors for all replies
1371 QHttpNetworkReply *currentReply = httpMessagePair.second;
1372 Q_ASSERT(currentReply);
1373 emit currentReply->sslErrors(errors);
1374 }
1375 }
1376#endif // QT_NO_SSL
1377 connection->d_func()->resumeConnection();
1378}
1379
1380void QHttpNetworkConnectionChannel::_q_preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator)
1381{
1382 connection->d_func()->pauseConnection();
1383
1384 if (pendingEncrypt && !reply)
1385 connection->d_func()->dequeueRequest(socket);
1386
1387 if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP) {
1388 if (reply)
1389 emit reply->preSharedKeyAuthenticationRequired(authenticator);
1390 } else {
1391 const auto h2RequestsToSendCopy = h2RequestsToSend;
1392 for (const auto &httpMessagePair : h2RequestsToSendCopy) {
1393 // emit SSL errors for all replies
1394 QHttpNetworkReply *currentReply = httpMessagePair.second;
1395 Q_ASSERT(currentReply);
1396 emit currentReply->preSharedKeyAuthenticationRequired(authenticator);
1397 }
1398 }
1399
1400 connection->d_func()->resumeConnection();
1401}
1402
1403void QHttpNetworkConnectionChannel::_q_encryptedBytesWritten(qint64 bytes)
1404{
1405 Q_UNUSED(bytes);
1406 // bytes have been written to the socket. write even more of them :)
1407 if (isSocketWriting())
1408 sendRequest();
1409 // otherwise we do nothing
1410}
1411
1412#endif
1413
1414void QHttpNetworkConnectionChannel::setConnection(QHttpNetworkConnection *c)
1415{
1416 // Inlining this function in the header leads to compiler error on
1417 // release-armv5, on at least timebox 9.2 and 10.1.
1418 connection = c;
1419}
1420
1421QT_END_NAMESPACE
1422
1423#include "moc_qhttpnetworkconnectionchannel_p.cpp"
1424

source code of qtbase/src/network/access/qhttpnetworkconnectionchannel.cpp