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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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