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