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