1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40//#define QNETWORKACCESSHTTPBACKEND_DEBUG
41
42#include "qnetworkreplyhttpimpl_p.h"
43#include "qnetworkaccessmanager_p.h"
44#include "qnetworkaccesscache_p.h"
45#include "qabstractnetworkcache.h"
46#include "qnetworkrequest.h"
47#include "qnetworkreply.h"
48#include "qnetworkrequest_p.h"
49#include "qnetworkcookie.h"
50#include "qnetworkcookie_p.h"
51#include "QtCore/qdatetime.h"
52#include "QtCore/qelapsedtimer.h"
53#include "QtNetwork/qsslconfiguration.h"
54#include "qhttpthreaddelegate_p.h"
55#include "qhsts_p.h"
56#include "qthread.h"
57#include "QtCore/qcoreapplication.h"
58
59#include <QtCore/private/qthread_p.h>
60
61#include "qnetworkcookiejar.h"
62#include "qnetconmonitor_p.h"
63
64#include <string.h> // for strchr
65
66QT_BEGIN_NAMESPACE
67
68class QNetworkProxy;
69
70static inline bool isSeparator(char c)
71{
72 static const char separators[] = "()<>@,;:\\\"/[]?={}";
73 return isLWS(c) || strchr(separators, c) != nullptr;
74}
75
76// ### merge with nextField in cookiejar.cpp
77static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &header)
78{
79 // The HTTP header is of the form:
80 // header = #1(directives)
81 // directives = token | value-directive
82 // value-directive = token "=" (token | quoted-string)
83 QHash<QByteArray, QByteArray> result;
84
85 int pos = 0;
86 while (true) {
87 // skip spaces
88 pos = nextNonWhitespace(header, pos);
89 if (pos == header.length())
90 return result; // end of parsing
91
92 // pos points to a non-whitespace
93 int comma = header.indexOf(',', pos);
94 int equal = header.indexOf('=', pos);
95 if (comma == pos || equal == pos)
96 // huh? Broken header.
97 return result;
98
99 // The key name is delimited by either a comma, an equal sign or the end
100 // of the header, whichever comes first
101 int end = comma;
102 if (end == -1)
103 end = header.length();
104 if (equal != -1 && end > equal)
105 end = equal; // equal sign comes before comma/end
106 QByteArray key = QByteArray(header.constData() + pos, end - pos).trimmed().toLower();
107 pos = end + 1;
108
109 if (uint(equal) < uint(comma)) {
110 // case: token "=" (token | quoted-string)
111 // skip spaces
112 pos = nextNonWhitespace(header, pos);
113 if (pos == header.length())
114 // huh? Broken header
115 return result;
116
117 QByteArray value;
118 value.reserve(header.length() - pos);
119 if (header.at(pos) == '"') {
120 // case: quoted-string
121 // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
122 // qdtext = <any TEXT except <">>
123 // quoted-pair = "\" CHAR
124 ++pos;
125 while (pos < header.length()) {
126 char c = header.at(pos);
127 if (c == '"') {
128 // end of quoted text
129 break;
130 } else if (c == '\\') {
131 ++pos;
132 if (pos >= header.length())
133 // broken header
134 return result;
135 c = header.at(pos);
136 }
137
138 value += c;
139 ++pos;
140 }
141 } else {
142 // case: token
143 while (pos < header.length()) {
144 char c = header.at(pos);
145 if (isSeparator(c))
146 break;
147 value += c;
148 ++pos;
149 }
150 }
151
152 result.insert(key, value);
153
154 // find the comma now:
155 comma = header.indexOf(',', pos);
156 if (comma == -1)
157 return result; // end of parsing
158 pos = comma + 1;
159 } else {
160 // case: token
161 // key is already set
162 result.insert(key, QByteArray());
163 }
164 }
165}
166
167#if QT_CONFIG(bearermanagement)
168static bool isSessionNeeded(const QUrl &url)
169{
170 if (QNetworkStatusMonitor::isEnabled()) {
171 // In case QNetworkStatus/QNetConManager are in business,
172 // no session, no bearer manager are involved.
173 return false;
174 }
175 // Connections to the local machine does not require a session
176 QString host = url.host().toLower();
177 return !QHostAddress(host).isLoopback() && host != QLatin1String("localhost")
178 && host != QSysInfo::machineHostName().toLower();
179}
180#endif // bearer management
181
182QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manager,
183 const QNetworkRequest& request,
184 QNetworkAccessManager::Operation& operation,
185 QIODevice* outgoingData)
186 : QNetworkReply(*new QNetworkReplyHttpImplPrivate, manager)
187{
188 Q_D(QNetworkReplyHttpImpl);
189 Q_ASSERT(manager);
190 d->manager = manager;
191 d->managerPrivate = manager->d_func();
192 d->request = request;
193 d->originalRequest = request;
194 d->operation = operation;
195 d->outgoingData = outgoingData;
196 d->url = request.url();
197#ifndef QT_NO_SSL
198 if (request.url().scheme() == QLatin1String("https"))
199 d->sslConfiguration.reset(new QSslConfiguration(request.sslConfiguration()));
200#endif
201
202 // FIXME Later maybe set to Unbuffered, especially if it is zerocopy or from cache?
203 QIODevice::open(QIODevice::ReadOnly);
204
205
206 // Internal code that does a HTTP reply for the synchronous Ajax
207 // in Qt WebKit.
208 QVariant synchronousHttpAttribute = request.attribute(
209 static_cast<QNetworkRequest::Attribute>(QNetworkRequest::SynchronousRequestAttribute));
210 if (synchronousHttpAttribute.isValid()) {
211 d->synchronous = synchronousHttpAttribute.toBool();
212 if (d->synchronous && outgoingData) {
213 // The synchronous HTTP is a corner case, we will put all upload data in one big QByteArray in the outgoingDataBuffer.
214 // Yes, this is not the most efficient thing to do, but on the other hand synchronous XHR needs to die anyway.
215 d->outgoingDataBuffer = QSharedPointer<QRingBuffer>::create();
216 qint64 previousDataSize = 0;
217 do {
218 previousDataSize = d->outgoingDataBuffer->size();
219 d->outgoingDataBuffer->append(d->outgoingData->readAll());
220 } while (d->outgoingDataBuffer->size() != previousDataSize);
221 d->_q_startOperation();
222 return;
223 }
224 }
225
226
227 if (outgoingData) {
228 // there is data to be uploaded, e.g. HTTP POST.
229
230 if (!d->outgoingData->isSequential()) {
231 // fixed size non-sequential (random-access)
232 // just start the operation
233 QMetaObject::invokeMethod(this, "_q_startOperation", Qt::QueuedConnection);
234 // FIXME make direct call?
235 } else {
236 bool bufferingDisallowed =
237 request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute,
238 false).toBool();
239
240 if (bufferingDisallowed) {
241 // if a valid content-length header for the request was supplied, we can disable buffering
242 // if not, we will buffer anyway
243 if (request.header(QNetworkRequest::ContentLengthHeader).isValid()) {
244 QMetaObject::invokeMethod(this, "_q_startOperation", Qt::QueuedConnection);
245 // FIXME make direct call?
246 } else {
247 d->state = d->Buffering;
248 QMetaObject::invokeMethod(this, "_q_bufferOutgoingData", Qt::QueuedConnection);
249 }
250 } else {
251 // _q_startOperation will be called when the buffering has finished.
252 d->state = d->Buffering;
253 QMetaObject::invokeMethod(this, "_q_bufferOutgoingData", Qt::QueuedConnection);
254 }
255 }
256 } else {
257 // No outgoing data (POST, ..)
258 d->_q_startOperation();
259 }
260}
261
262QNetworkReplyHttpImpl::~QNetworkReplyHttpImpl()
263{
264 // This will do nothing if the request was already finished or aborted
265 emit abortHttpRequest();
266}
267
268void QNetworkReplyHttpImpl::close()
269{
270 Q_D(QNetworkReplyHttpImpl);
271
272 if (d->state == QNetworkReplyPrivate::Aborted ||
273 d->state == QNetworkReplyPrivate::Finished)
274 return;
275
276 // According to the documentation close only stops the download
277 // by closing we can ignore the download part and continue uploading.
278 QNetworkReply::close();
279
280 // call finished which will emit signals
281 // FIXME shouldn't this be emitted Queued?
282 d->error(OperationCanceledError, tr("Operation canceled"));
283 d->finished();
284}
285
286void QNetworkReplyHttpImpl::abort()
287{
288 Q_D(QNetworkReplyHttpImpl);
289 // FIXME
290 if (d->state == QNetworkReplyPrivate::Finished || d->state == QNetworkReplyPrivate::Aborted)
291 return;
292
293 QNetworkReply::close();
294
295 if (d->state != QNetworkReplyPrivate::Finished) {
296 // call finished which will emit signals
297 // FIXME shouldn't this be emitted Queued?
298 d->error(OperationCanceledError, tr("Operation canceled"));
299
300 // If state is WaitingForSession, calling finished has no effect
301 if (d->state == QNetworkReplyPrivate::WaitingForSession)
302 d->state = QNetworkReplyPrivate::Working;
303 d->finished();
304 }
305
306 d->state = QNetworkReplyPrivate::Aborted;
307
308 emit abortHttpRequest();
309}
310
311qint64 QNetworkReplyHttpImpl::bytesAvailable() const
312{
313 Q_D(const QNetworkReplyHttpImpl);
314
315 // if we load from cache device
316 if (d->cacheLoadDevice) {
317 return QNetworkReply::bytesAvailable() + d->cacheLoadDevice->bytesAvailable();
318 }
319
320 // zerocopy buffer
321 if (d->downloadZerocopyBuffer) {
322 return QNetworkReply::bytesAvailable() + d->downloadBufferCurrentSize - d->downloadBufferReadPosition;
323 }
324
325 // normal buffer
326 return QNetworkReply::bytesAvailable();
327}
328
329bool QNetworkReplyHttpImpl::isSequential () const
330{
331 // FIXME In the cache of a cached load or the zero-copy buffer we could actually be non-sequential.
332 // FIXME however this requires us to implement stuff like seek() too.
333 return true;
334}
335
336qint64 QNetworkReplyHttpImpl::size() const
337{
338 // FIXME At some point, this could return a proper value, e.g. if we're non-sequential.
339 return QNetworkReply::size();
340}
341
342qint64 QNetworkReplyHttpImpl::readData(char* data, qint64 maxlen)
343{
344 Q_D(QNetworkReplyHttpImpl);
345
346 // cacheload device
347 if (d->cacheLoadDevice) {
348 // FIXME bytesdownloaded, position etc?
349
350 qint64 ret = d->cacheLoadDevice->read(data, maxlen);
351 return ret;
352 }
353
354 // zerocopy buffer
355 if (d->downloadZerocopyBuffer) {
356 // FIXME bytesdownloaded, position etc?
357
358 qint64 howMuch = qMin(maxlen, (d->downloadBufferCurrentSize - d->downloadBufferReadPosition));
359 memcpy(data, d->downloadZerocopyBuffer + d->downloadBufferReadPosition, howMuch);
360 d->downloadBufferReadPosition += howMuch;
361 return howMuch;
362
363 }
364
365 // normal buffer
366 if (d->state == d->Finished || d->state == d->Aborted)
367 return -1;
368
369 qint64 wasBuffered = d->bytesBuffered;
370 d->bytesBuffered = 0;
371 if (readBufferSize())
372 emit readBufferFreed(wasBuffered);
373 return 0;
374}
375
376void QNetworkReplyHttpImpl::setReadBufferSize(qint64 size)
377{
378 QNetworkReply::setReadBufferSize(size);
379 emit readBufferSizeChanged(size);
380 return;
381}
382
383bool QNetworkReplyHttpImpl::canReadLine () const
384{
385 Q_D(const QNetworkReplyHttpImpl);
386
387 if (QNetworkReply::canReadLine())
388 return true;
389
390 if (d->cacheLoadDevice)
391 return d->cacheLoadDevice->canReadLine();
392
393 if (d->downloadZerocopyBuffer)
394 return memchr(d->downloadZerocopyBuffer + d->downloadBufferReadPosition, '\n', d->downloadBufferCurrentSize - d->downloadBufferReadPosition);
395
396 return false;
397}
398
399#ifndef QT_NO_SSL
400void QNetworkReplyHttpImpl::ignoreSslErrors()
401{
402 Q_D(QNetworkReplyHttpImpl);
403 Q_ASSERT(d->managerPrivate);
404
405 if (d->managerPrivate->stsEnabled && d->managerPrivate->stsCache.isKnownHost(url())) {
406 // We cannot ignore any Security Transport-related errors for this host.
407 return;
408 }
409
410 d->pendingIgnoreAllSslErrors = true;
411}
412
413void QNetworkReplyHttpImpl::ignoreSslErrorsImplementation(const QList<QSslError> &errors)
414{
415 Q_D(QNetworkReplyHttpImpl);
416 Q_ASSERT(d->managerPrivate);
417
418 if (d->managerPrivate->stsEnabled && d->managerPrivate->stsCache.isKnownHost(url())) {
419 // We cannot ignore any Security Transport-related errors for this host.
420 return;
421 }
422
423 // the pending list is set if QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
424 // is called before QNetworkAccessManager::get() (or post(), etc.)
425 d->pendingIgnoreSslErrorsList = errors;
426}
427
428void QNetworkReplyHttpImpl::setSslConfigurationImplementation(const QSslConfiguration &newconfig)
429{
430 // Setting a SSL configuration on a reply is not supported. The user needs to set
431 // her/his QSslConfiguration on the QNetworkRequest.
432 Q_UNUSED(newconfig);
433}
434
435void QNetworkReplyHttpImpl::sslConfigurationImplementation(QSslConfiguration &configuration) const
436{
437 Q_D(const QNetworkReplyHttpImpl);
438 if (d->sslConfiguration.data())
439 configuration = *d->sslConfiguration;
440 else
441 configuration = request().sslConfiguration();
442}
443#endif
444
445QNetworkReplyHttpImplPrivate::QNetworkReplyHttpImplPrivate()
446 : QNetworkReplyPrivate()
447 , manager(nullptr)
448 , managerPrivate(nullptr)
449 , synchronous(false)
450 , state(Idle)
451 , statusCode(0)
452 , uploadByteDevicePosition(false)
453 , uploadDeviceChoking(false)
454 , outgoingData(nullptr)
455 , bytesUploaded(-1)
456 , cacheLoadDevice(nullptr)
457 , loadingFromCache(false)
458 , cacheSaveDevice(nullptr)
459 , cacheEnabled(false)
460 , resumeOffset(0)
461 , preMigrationDownloaded(-1)
462 , bytesDownloaded(0)
463 , bytesBuffered(0)
464 , downloadBufferReadPosition(0)
465 , downloadBufferCurrentSize(0)
466 , downloadZerocopyBuffer(nullptr)
467 , pendingDownloadDataEmissions(QSharedPointer<QAtomicInt>::create())
468 , pendingDownloadProgressEmissions(QSharedPointer<QAtomicInt>::create())
469 #ifndef QT_NO_SSL
470 , pendingIgnoreAllSslErrors(false)
471 #endif
472
473{
474}
475
476QNetworkReplyHttpImplPrivate::~QNetworkReplyHttpImplPrivate()
477{
478}
479
480/*
481 For a given httpRequest
482 1) If AlwaysNetwork, return
483 2) If we have a cache entry for this url populate headers so the server can return 304
484 3) Calculate if response_is_fresh and if so send the cache and set loadedFromCache to true
485 */
486bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &httpRequest)
487{
488 QNetworkRequest::CacheLoadControl CacheLoadControlAttribute =
489 (QNetworkRequest::CacheLoadControl)request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt();
490 if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) {
491 // If the request does not already specify preferred cache-control
492 // force reload from the network and tell any caching proxy servers to reload too
493 if (!request.rawHeaderList().contains("Cache-Control")) {
494 httpRequest.setHeaderField("Cache-Control", "no-cache");
495 httpRequest.setHeaderField("Pragma", "no-cache");
496 }
497 return false;
498 }
499
500 // The disk cache API does not currently support partial content retrieval.
501 // That is why we don't use the disk cache for any such requests.
502 if (request.hasRawHeader("Range"))
503 return false;
504
505 QAbstractNetworkCache *nc = managerPrivate->networkCache;
506 if (!nc)
507 return false; // no local cache
508
509 QNetworkCacheMetaData metaData = nc->metaData(httpRequest.url());
510 if (!metaData.isValid())
511 return false; // not in cache
512
513 if (!metaData.saveToDisk())
514 return false;
515
516 QNetworkHeadersPrivate cacheHeaders;
517 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
518 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
519
520 it = cacheHeaders.findRawHeader("etag");
521 if (it != cacheHeaders.rawHeaders.constEnd())
522 httpRequest.setHeaderField("If-None-Match", it->second);
523
524 QDateTime lastModified = metaData.lastModified();
525 if (lastModified.isValid())
526 httpRequest.setHeaderField("If-Modified-Since", QNetworkHeadersPrivate::toHttpDate(lastModified));
527
528 it = cacheHeaders.findRawHeader("Cache-Control");
529 if (it != cacheHeaders.rawHeaders.constEnd()) {
530 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
531 if (cacheControl.contains("must-revalidate"))
532 return false;
533 if (cacheControl.contains("no-cache"))
534 return false;
535 }
536
537 QDateTime currentDateTime = QDateTime::currentDateTimeUtc();
538 QDateTime expirationDate = metaData.expirationDate();
539
540 bool response_is_fresh;
541 if (!expirationDate.isValid()) {
542 /*
543 * age_value
544 * is the value of Age: header received by the cache with
545 * this response.
546 * date_value
547 * is the value of the origin server's Date: header
548 * request_time
549 * is the (local) time when the cache made the request
550 * that resulted in this cached response
551 * response_time
552 * is the (local) time when the cache received the
553 * response
554 * now
555 * is the current (local) time
556 */
557 qint64 age_value = 0;
558 it = cacheHeaders.findRawHeader("age");
559 if (it != cacheHeaders.rawHeaders.constEnd())
560 age_value = it->second.toLongLong();
561
562 QDateTime dateHeader;
563 qint64 date_value = 0;
564 it = cacheHeaders.findRawHeader("date");
565 if (it != cacheHeaders.rawHeaders.constEnd()) {
566 dateHeader = QNetworkHeadersPrivate::fromHttpDate(it->second);
567 date_value = dateHeader.toSecsSinceEpoch();
568 }
569
570 qint64 now = currentDateTime.toSecsSinceEpoch();
571 qint64 request_time = now;
572 qint64 response_time = now;
573
574 // Algorithm from RFC 2616 section 13.2.3
575 qint64 apparent_age = qMax<qint64>(0, response_time - date_value);
576 qint64 corrected_received_age = qMax(apparent_age, age_value);
577 qint64 response_delay = response_time - request_time;
578 qint64 corrected_initial_age = corrected_received_age + response_delay;
579 qint64 resident_time = now - response_time;
580 qint64 current_age = corrected_initial_age + resident_time;
581
582 qint64 freshness_lifetime = 0;
583
584 // RFC 2616 13.2.4 Expiration Calculations
585 if (lastModified.isValid() && dateHeader.isValid()) {
586 qint64 diff = lastModified.secsTo(dateHeader);
587 freshness_lifetime = diff / 10;
588 if (httpRequest.headerField("Warning").isEmpty()) {
589 QDateTime dt = currentDateTime.addSecs(current_age);
590 if (currentDateTime.daysTo(dt) > 1)
591 httpRequest.setHeaderField("Warning", "113");
592 }
593 }
594
595 // the cache-saving code below sets the freshness_lifetime with (dateHeader - last_modified) / 10
596 // if "last-modified" is present, or to Expires otherwise
597 response_is_fresh = (freshness_lifetime > current_age);
598 } else {
599 // expiration date was calculated earlier (e.g. when storing object to the cache)
600 response_is_fresh = currentDateTime.secsTo(expirationDate) >= 0;
601 }
602
603 if (!response_is_fresh)
604 return false;
605
606#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
607 qDebug() << "response_is_fresh" << CacheLoadControlAttribute;
608#endif
609 return sendCacheContents(metaData);
610}
611
612QHttpNetworkRequest::Priority QNetworkReplyHttpImplPrivate::convert(const QNetworkRequest::Priority& prio)
613{
614 switch (prio) {
615 case QNetworkRequest::LowPriority:
616 return QHttpNetworkRequest::LowPriority;
617 case QNetworkRequest::HighPriority:
618 return QHttpNetworkRequest::HighPriority;
619 case QNetworkRequest::NormalPriority:
620 default:
621 return QHttpNetworkRequest::NormalPriority;
622 }
623}
624
625void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpRequest)
626{
627 Q_Q(QNetworkReplyHttpImpl);
628
629 QThread *thread = nullptr;
630 if (synchronous) {
631 // A synchronous HTTP request uses its own thread
632 thread = new QThread();
633 thread->setObjectName(QStringLiteral("Qt HTTP synchronous thread"));
634 QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
635 thread->start();
636 } else {
637 // We use the manager-global thread.
638 // At some point we could switch to having multiple threads if it makes sense.
639 thread = managerPrivate->createThread();
640 }
641
642 QUrl url = newHttpRequest.url();
643 httpRequest.setUrl(url);
644 httpRequest.setRedirectCount(newHttpRequest.maximumRedirectsAllowed());
645
646 QString scheme = url.scheme();
647 bool ssl = (scheme == QLatin1String("https")
648 || scheme == QLatin1String("preconnect-https"));
649 q->setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl);
650 httpRequest.setSsl(ssl);
651
652 bool preConnect = (scheme == QLatin1String("preconnect-http")
653 || scheme == QLatin1String("preconnect-https"));
654 httpRequest.setPreConnect(preConnect);
655
656#ifndef QT_NO_NETWORKPROXY
657 QNetworkProxy transparentProxy, cacheProxy;
658
659 // FIXME the proxy stuff should be done in the HTTP thread
660 const auto proxies = managerPrivate->queryProxy(QNetworkProxyQuery(newHttpRequest.url()));
661 for (const QNetworkProxy &p : proxies) {
662 // use the first proxy that works
663 // for non-encrypted connections, any transparent or HTTP proxy
664 // for encrypted, only transparent proxies
665 if (!ssl
666 && (p.capabilities() & QNetworkProxy::CachingCapability)
667 && (p.type() == QNetworkProxy::HttpProxy ||
668 p.type() == QNetworkProxy::HttpCachingProxy)) {
669 cacheProxy = p;
670 transparentProxy = QNetworkProxy::NoProxy;
671 break;
672 }
673 if (p.isTransparentProxy()) {
674 transparentProxy = p;
675 cacheProxy = QNetworkProxy::NoProxy;
676 break;
677 }
678 }
679
680 // check if at least one of the proxies
681 if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
682 cacheProxy.type() == QNetworkProxy::DefaultProxy) {
683 // unsuitable proxies
684 QMetaObject::invokeMethod(q, "_q_error", synchronous ? Qt::DirectConnection : Qt::QueuedConnection,
685 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError),
686 Q_ARG(QString, QNetworkReplyHttpImpl::tr("No suitable proxy found")));
687 QMetaObject::invokeMethod(q, "_q_finished", synchronous ? Qt::DirectConnection : Qt::QueuedConnection);
688 return;
689 }
690#endif
691
692 auto redirectPolicy = QNetworkRequest::ManualRedirectPolicy;
693 const QVariant value = newHttpRequest.attribute(QNetworkRequest::RedirectPolicyAttribute);
694 if (value.isValid())
695 redirectPolicy = value.value<QNetworkRequest::RedirectPolicy>();
696 else if (newHttpRequest.attribute(QNetworkRequest::FollowRedirectsAttribute).toBool())
697 redirectPolicy = QNetworkRequest::NoLessSafeRedirectPolicy;
698
699 httpRequest.setRedirectPolicy(redirectPolicy);
700
701 httpRequest.setPriority(convert(newHttpRequest.priority()));
702
703 switch (operation) {
704 case QNetworkAccessManager::GetOperation:
705 httpRequest.setOperation(QHttpNetworkRequest::Get);
706 if (loadFromCacheIfAllowed(httpRequest))
707 return; // no need to send the request! :)
708 break;
709
710 case QNetworkAccessManager::HeadOperation:
711 httpRequest.setOperation(QHttpNetworkRequest::Head);
712 if (loadFromCacheIfAllowed(httpRequest))
713 return; // no need to send the request! :)
714 break;
715
716 case QNetworkAccessManager::PostOperation:
717 invalidateCache();
718 httpRequest.setOperation(QHttpNetworkRequest::Post);
719 createUploadByteDevice();
720 break;
721
722 case QNetworkAccessManager::PutOperation:
723 invalidateCache();
724 httpRequest.setOperation(QHttpNetworkRequest::Put);
725 createUploadByteDevice();
726 break;
727
728 case QNetworkAccessManager::DeleteOperation:
729 invalidateCache();
730 httpRequest.setOperation(QHttpNetworkRequest::Delete);
731 break;
732
733 case QNetworkAccessManager::CustomOperation:
734 invalidateCache(); // for safety reasons, we don't know what the operation does
735 httpRequest.setOperation(QHttpNetworkRequest::Custom);
736 createUploadByteDevice();
737 httpRequest.setCustomVerb(newHttpRequest.attribute(
738 QNetworkRequest::CustomVerbAttribute).toByteArray());
739 break;
740
741 default:
742 break; // can't happen
743 }
744
745 QList<QByteArray> headers = newHttpRequest.rawHeaderList();
746 if (resumeOffset != 0) {
747 const int rangeIndex = headers.indexOf("Range");
748 if (rangeIndex != -1) {
749 // Need to adjust resume offset for user specified range
750
751 headers.removeAt(rangeIndex);
752
753 // We've already verified that requestRange starts with "bytes=", see canResume.
754 QByteArray requestRange = newHttpRequest.rawHeader("Range").mid(6);
755
756 int index = requestRange.indexOf('-');
757
758 quint64 requestStartOffset = requestRange.left(index).toULongLong();
759 quint64 requestEndOffset = requestRange.mid(index + 1).toULongLong();
760
761 requestRange = "bytes=" + QByteArray::number(resumeOffset + requestStartOffset) +
762 '-' + QByteArray::number(requestEndOffset);
763
764 httpRequest.setHeaderField("Range", requestRange);
765 } else {
766 httpRequest.setHeaderField("Range", "bytes=" + QByteArray::number(resumeOffset) + '-');
767 }
768 }
769
770 for (const QByteArray &header : qAsConst(headers))
771 httpRequest.setHeaderField(header, newHttpRequest.rawHeader(header));
772
773 if (newHttpRequest.attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool())
774 httpRequest.setPipeliningAllowed(true);
775
776 if (request.attribute(QNetworkRequest::SpdyAllowedAttribute).toBool())
777 httpRequest.setSPDYAllowed(true);
778
779 if (request.attribute(QNetworkRequest::HTTP2AllowedAttribute).toBool())
780 httpRequest.setHTTP2Allowed(true);
781
782 if (request.attribute(QNetworkRequest::Http2DirectAttribute).toBool()) {
783 // Intentionally mutually exclusive - cannot be both direct and 'allowed'
784 httpRequest.setHTTP2Direct(true);
785 httpRequest.setHTTP2Allowed(false);
786 }
787
788 if (static_cast<QNetworkRequest::LoadControl>
789 (newHttpRequest.attribute(QNetworkRequest::AuthenticationReuseAttribute,
790 QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
791 httpRequest.setWithCredentials(false);
792
793 if (request.attribute(QNetworkRequest::EmitAllUploadProgressSignalsAttribute).toBool())
794 emitAllUploadProgressSignals = true;
795
796 httpRequest.setPeerVerifyName(newHttpRequest.peerVerifyName());
797
798 // Create the HTTP thread delegate
799 QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
800 // Propagate Http/2 settings if any
801 const QVariant blob(manager->property(Http2::http2ParametersPropertyName));
802 if (blob.isValid() && blob.canConvert<Http2::ProtocolParameters>())
803 delegate->http2Parameters = blob.value<Http2::ProtocolParameters>();
804#ifndef QT_NO_BEARERMANAGEMENT
805 if (!QNetworkStatusMonitor::isEnabled())
806 delegate->networkSession = managerPrivate->getNetworkSession();
807#endif
808
809 // For the synchronous HTTP, this is the normal way the delegate gets deleted
810 // For the asynchronous HTTP this is a safety measure, the delegate deletes itself when HTTP is finished
811 QObject::connect(thread, SIGNAL(finished()), delegate, SLOT(deleteLater()));
812
813 // Set the properties it needs
814 delegate->httpRequest = httpRequest;
815#ifndef QT_NO_NETWORKPROXY
816 delegate->cacheProxy = cacheProxy;
817 delegate->transparentProxy = transparentProxy;
818#endif
819 delegate->ssl = ssl;
820#ifndef QT_NO_SSL
821 if (ssl)
822 delegate->incomingSslConfiguration.reset(new QSslConfiguration(newHttpRequest.sslConfiguration()));
823#endif
824
825 // Do we use synchronous HTTP?
826 delegate->synchronous = synchronous;
827
828 // The authentication manager is used to avoid the BlockingQueuedConnection communication
829 // from HTTP thread to user thread in some cases.
830 delegate->authenticationManager = managerPrivate->authenticationManager;
831
832 if (!synchronous) {
833 // Tell our zerocopy policy to the delegate
834 QVariant downloadBufferMaximumSizeAttribute = newHttpRequest.attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute);
835 if (downloadBufferMaximumSizeAttribute.isValid()) {
836 delegate->downloadBufferMaximumSize = downloadBufferMaximumSizeAttribute.toLongLong();
837 } else {
838 // If there is no MaximumDownloadBufferSizeAttribute set (which is for the majority
839 // of QNetworkRequest) then we can assume we'll do it anyway for small HTTP replies.
840 // This helps with performance and memory fragmentation.
841 delegate->downloadBufferMaximumSize = 128*1024;
842 }
843
844
845 // These atomic integers are used for signal compression
846 delegate->pendingDownloadData = pendingDownloadDataEmissions;
847 delegate->pendingDownloadProgress = pendingDownloadProgressEmissions;
848
849 // Connect the signals of the delegate to us
850 QObject::connect(delegate, SIGNAL(downloadData(QByteArray)),
851 q, SLOT(replyDownloadData(QByteArray)),
852 Qt::QueuedConnection);
853 QObject::connect(delegate, SIGNAL(downloadFinished()),
854 q, SLOT(replyFinished()),
855 Qt::QueuedConnection);
856 QObject::connect(delegate, SIGNAL(downloadMetaData(QList<QPair<QByteArray,QByteArray> >,
857 int, QString, bool,
858 QSharedPointer<char>, qint64, qint64,
859 bool)),
860 q, SLOT(replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,
861 int, QString, bool,
862 QSharedPointer<char>, qint64, qint64, bool)),
863 Qt::QueuedConnection);
864 QObject::connect(delegate, SIGNAL(downloadProgress(qint64,qint64)),
865 q, SLOT(replyDownloadProgressSlot(qint64,qint64)),
866 Qt::QueuedConnection);
867 QObject::connect(delegate, SIGNAL(error(QNetworkReply::NetworkError,QString)),
868 q, SLOT(httpError(QNetworkReply::NetworkError,QString)),
869 Qt::QueuedConnection);
870 QObject::connect(delegate, SIGNAL(redirected(QUrl,int,int)),
871 q, SLOT(onRedirected(QUrl,int,int)),
872 Qt::QueuedConnection);
873
874 QObject::connect(q, SIGNAL(redirectAllowed()), q, SLOT(followRedirect()),
875 Qt::QueuedConnection);
876
877#ifndef QT_NO_SSL
878 QObject::connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)),
879 q, SLOT(replySslConfigurationChanged(QSslConfiguration)),
880 Qt::QueuedConnection);
881#endif
882 // Those need to report back, therefore BlockingQueuedConnection
883 QObject::connect(delegate, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
884 q, SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
885 Qt::BlockingQueuedConnection);
886#ifndef QT_NO_NETWORKPROXY
887 QObject::connect(delegate, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
888 q, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
889 Qt::BlockingQueuedConnection);
890#endif
891#ifndef QT_NO_SSL
892 QObject::connect(delegate, SIGNAL(encrypted()), q, SLOT(replyEncrypted()),
893 Qt::BlockingQueuedConnection);
894 QObject::connect(delegate, SIGNAL(sslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
895 q, SLOT(replySslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
896 Qt::BlockingQueuedConnection);
897 QObject::connect(delegate, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
898 q, SLOT(replyPreSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator*)),
899 Qt::BlockingQueuedConnection);
900#endif
901 // This signal we will use to start the request.
902 QObject::connect(q, SIGNAL(startHttpRequest()), delegate, SLOT(startRequest()));
903 QObject::connect(q, SIGNAL(abortHttpRequest()), delegate, SLOT(abortRequest()));
904
905 // To throttle the connection.
906 QObject::connect(q, SIGNAL(readBufferSizeChanged(qint64)), delegate, SLOT(readBufferSizeChanged(qint64)));
907 QObject::connect(q, SIGNAL(readBufferFreed(qint64)), delegate, SLOT(readBufferFreed(qint64)));
908
909 if (uploadByteDevice) {
910 QNonContiguousByteDeviceThreadForwardImpl *forwardUploadDevice =
911 new QNonContiguousByteDeviceThreadForwardImpl(uploadByteDevice->atEnd(), uploadByteDevice->size());
912 forwardUploadDevice->setParent(delegate); // needed to make sure it is moved on moveToThread()
913 delegate->httpRequest.setUploadByteDevice(forwardUploadDevice);
914
915 // If the device in the user thread claims it has more data, keep the flow to HTTP thread going
916 QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
917 q, SLOT(uploadByteDeviceReadyReadSlot()),
918 Qt::QueuedConnection);
919
920 // From user thread to http thread:
921 QObject::connect(q, SIGNAL(haveUploadData(qint64,QByteArray,bool,qint64)),
922 forwardUploadDevice, SLOT(haveDataSlot(qint64,QByteArray,bool,qint64)), Qt::QueuedConnection);
923 QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
924 forwardUploadDevice, SIGNAL(readyRead()),
925 Qt::QueuedConnection);
926
927 // From http thread to user thread:
928 QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)),
929 q, SLOT(wantUploadDataSlot(qint64)));
930 QObject::connect(forwardUploadDevice,SIGNAL(processedData(qint64,qint64)),
931 q, SLOT(sentUploadDataSlot(qint64,qint64)));
932 QObject::connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
933 q, SLOT(resetUploadDataSlot(bool*)),
934 Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
935 }
936 } else if (synchronous) {
937 QObject::connect(q, SIGNAL(startHttpRequestSynchronously()), delegate, SLOT(startRequestSynchronously()), Qt::BlockingQueuedConnection);
938
939 if (uploadByteDevice) {
940 // For the synchronous HTTP use case the use thread (this one here) is blocked
941 // so we cannot use the asynchronous upload architecture.
942 // We therefore won't use the QNonContiguousByteDeviceThreadForwardImpl but directly
943 // use the uploadByteDevice provided to us by the QNetworkReplyImpl.
944 // The code that is in start() makes sure it is safe to use from a thread
945 // since it only wraps a QRingBuffer
946 delegate->httpRequest.setUploadByteDevice(uploadByteDevice.data());
947 }
948 }
949
950
951 // Move the delegate to the http thread
952 delegate->moveToThread(thread);
953 // This call automatically moves the uploadDevice too for the asynchronous case.
954
955 // Prepare timers for progress notifications
956 downloadProgressSignalChoke.start();
957 uploadProgressSignalChoke.invalidate();
958
959 // Send an signal to the delegate so it starts working in the other thread
960 if (synchronous) {
961 emit q->startHttpRequestSynchronously(); // This one is BlockingQueuedConnection, so it will return when all work is done
962
963 if (delegate->incomingErrorCode != QNetworkReply::NoError) {
964 replyDownloadMetaData
965 (delegate->incomingHeaders,
966 delegate->incomingStatusCode,
967 delegate->incomingReasonPhrase,
968 delegate->isPipeliningUsed,
969 QSharedPointer<char>(),
970 delegate->incomingContentLength,
971 delegate->removedContentLength,
972 delegate->isSpdyUsed);
973 replyDownloadData(delegate->synchronousDownloadData);
974 httpError(delegate->incomingErrorCode, delegate->incomingErrorDetail);
975 } else {
976 replyDownloadMetaData
977 (delegate->incomingHeaders,
978 delegate->incomingStatusCode,
979 delegate->incomingReasonPhrase,
980 delegate->isPipeliningUsed,
981 QSharedPointer<char>(),
982 delegate->incomingContentLength,
983 delegate->removedContentLength,
984 delegate->isSpdyUsed);
985 replyDownloadData(delegate->synchronousDownloadData);
986 }
987
988 thread->quit();
989 thread->wait(5000);
990 if (thread->isFinished())
991 delete thread;
992 else
993 QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
994
995 finished();
996 } else {
997 emit q->startHttpRequest(); // Signal to the HTTP thread and go back to user.
998 }
999}
1000
1001void QNetworkReplyHttpImplPrivate::invalidateCache()
1002{
1003 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1004 if (nc)
1005 nc->remove(httpRequest.url());
1006}
1007
1008void QNetworkReplyHttpImplPrivate::initCacheSaveDevice()
1009{
1010 Q_Q(QNetworkReplyHttpImpl);
1011
1012 // The disk cache does not support partial content, so don't even try to
1013 // save any such content into the cache.
1014 if (q->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 206) {
1015 cacheEnabled = false;
1016 return;
1017 }
1018
1019 // save the meta data
1020 QNetworkCacheMetaData metaData;
1021 metaData.setUrl(url);
1022 metaData = fetchCacheMetaData(metaData);
1023
1024 // save the redirect request also in the cache
1025 QVariant redirectionTarget = q->attribute(QNetworkRequest::RedirectionTargetAttribute);
1026 if (redirectionTarget.isValid()) {
1027 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
1028 attributes.insert(QNetworkRequest::RedirectionTargetAttribute, redirectionTarget);
1029 metaData.setAttributes(attributes);
1030 }
1031
1032 cacheSaveDevice = managerPrivate->networkCache->prepare(metaData);
1033
1034 if (cacheSaveDevice)
1035 q->connect(cacheSaveDevice, SIGNAL(aboutToClose()), SLOT(_q_cacheSaveDeviceAboutToClose()));
1036
1037 if (!cacheSaveDevice || (cacheSaveDevice && !cacheSaveDevice->isOpen())) {
1038 if (Q_UNLIKELY(cacheSaveDevice && !cacheSaveDevice->isOpen()))
1039 qCritical("QNetworkReplyImpl: network cache returned a device that is not open -- "
1040 "class %s probably needs to be fixed",
1041 managerPrivate->networkCache->metaObject()->className());
1042
1043 managerPrivate->networkCache->remove(url);
1044 cacheSaveDevice = nullptr;
1045 cacheEnabled = false;
1046 }
1047}
1048
1049void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d)
1050{
1051 Q_Q(QNetworkReplyHttpImpl);
1052
1053 // If we're closed just ignore this data
1054 if (!q->isOpen())
1055 return;
1056
1057 int pendingSignals = (int)pendingDownloadDataEmissions->fetchAndAddAcquire(-1) - 1;
1058
1059 if (pendingSignals > 0) {
1060 // Some more signal emissions to this slot are pending.
1061 // Instead of writing the downstream data, we wait
1062 // and do it in the next call we get
1063 // (signal comppression)
1064 pendingDownloadData.append(d);
1065 return;
1066 }
1067
1068 pendingDownloadData.append(d);
1069 d.clear();
1070 // We need to usa a copy for calling writeDownstreamData as we could
1071 // possibly recurse into this this function when we call
1072 // appendDownstreamDataSignalEmissions because the user might call
1073 // processEvents() or spin an event loop when this occur.
1074 QByteDataBuffer pendingDownloadDataCopy = pendingDownloadData;
1075 pendingDownloadData.clear();
1076
1077 if (cacheEnabled && isCachingAllowed() && !cacheSaveDevice) {
1078 initCacheSaveDevice();
1079 }
1080
1081 qint64 bytesWritten = 0;
1082 for (int i = 0; i < pendingDownloadDataCopy.bufferCount(); i++) {
1083 QByteArray const &item = pendingDownloadDataCopy[i];
1084
1085 // This is going to look a little strange. When downloading data while a
1086 // HTTP redirect is happening (and enabled), we write the redirect
1087 // response to the cache. However, we do not append it to our internal
1088 // buffer as that will contain the response data only for the final
1089 // response
1090 if (cacheSaveDevice)
1091 cacheSaveDevice->write(item.constData(), item.size());
1092
1093 if (!isHttpRedirectResponse())
1094 buffer.append(item);
1095
1096 bytesWritten += item.size();
1097 }
1098 bytesBuffered += bytesWritten;
1099 pendingDownloadDataCopy.clear();
1100
1101 QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
1102 if (preMigrationDownloaded != Q_INT64_C(-1))
1103 totalSize = totalSize.toLongLong() + preMigrationDownloaded;
1104
1105 if (isHttpRedirectResponse())
1106 return;
1107
1108 bytesDownloaded += bytesWritten;
1109
1110 emit q->readyRead();
1111 // emit readyRead before downloadProgress incase this will cause events to be
1112 // processed and we get into a recursive call (as in QProgressDialog).
1113 if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
1114 downloadProgressSignalChoke.restart();
1115 emit q->downloadProgress(bytesDownloaded,
1116 totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
1117 }
1118
1119}
1120
1121void QNetworkReplyHttpImplPrivate::replyFinished()
1122{
1123 // We are already loading from cache, we still however
1124 // got this signal because it was posted already
1125 if (loadingFromCache)
1126 return;
1127
1128 finished();
1129}
1130
1131QNetworkAccessManager::Operation QNetworkReplyHttpImplPrivate::getRedirectOperation(QNetworkAccessManager::Operation currentOp, int httpStatus)
1132{
1133 // HTTP status code can be used to decide if we can redirect with a GET
1134 // operation or not. See http://www.ietf.org/rfc/rfc2616.txt [Sec 10.3] for
1135 // more details
1136
1137 // We MUST keep using the verb that was used originally when being redirected with 307 or 308.
1138 if (httpStatus == 307 || httpStatus == 308)
1139 return currentOp;
1140
1141 switch (currentOp) {
1142 case QNetworkAccessManager::HeadOperation:
1143 return QNetworkAccessManager::HeadOperation;
1144 default:
1145 break;
1146 }
1147 // Use GET for everything else.
1148 return QNetworkAccessManager::GetOperation;
1149}
1150
1151bool QNetworkReplyHttpImplPrivate::isHttpRedirectResponse() const
1152{
1153 return httpRequest.isFollowRedirects() && QHttpNetworkReply::isHttpRedirect(statusCode);
1154}
1155
1156QNetworkRequest QNetworkReplyHttpImplPrivate::createRedirectRequest(const QNetworkRequest &originalRequest,
1157 const QUrl &url,
1158 int maxRedirectsRemaining)
1159{
1160 QNetworkRequest newRequest(originalRequest);
1161 newRequest.setUrl(url);
1162 newRequest.setMaximumRedirectsAllowed(maxRedirectsRemaining);
1163
1164 return newRequest;
1165}
1166
1167void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int httpStatus, int maxRedirectsRemaining)
1168{
1169 Q_Q(QNetworkReplyHttpImpl);
1170 Q_ASSERT(manager);
1171 Q_ASSERT(managerPrivate);
1172
1173 if (isFinished)
1174 return;
1175
1176 const QString schemeBefore(url.scheme());
1177 if (httpRequest.isFollowRedirects()) // update the reply's url as it could've changed
1178 url = redirectUrl;
1179
1180 if (managerPrivate->stsEnabled && managerPrivate->stsCache.isKnownHost(url)) {
1181 // RFC6797, 8.3:
1182 // The UA MUST replace the URI scheme with "https" [RFC2818],
1183 // and if the URI contains an explicit port component of "80",
1184 // then the UA MUST convert the port component to be "443", or
1185 // if the URI contains an explicit port component that is not
1186 // equal to "80", the port component value MUST be preserved;
1187 // otherwise, if the URI does not contain an explicit port
1188 // component, the UA MUST NOT add one.
1189 url.setScheme(QLatin1String("https"));
1190 if (url.port() == 80)
1191 url.setPort(443);
1192 }
1193
1194 const bool isLessSafe = schemeBefore == QLatin1String("https")
1195 && url.scheme() == QLatin1String("http");
1196 if (httpRequest.redirectPolicy() == QNetworkRequest::NoLessSafeRedirectPolicy
1197 && isLessSafe) {
1198 error(QNetworkReply::InsecureRedirectError,
1199 QCoreApplication::translate("QHttp", "Insecure redirect"));
1200 return;
1201 }
1202
1203 redirectRequest = createRedirectRequest(originalRequest, url, maxRedirectsRemaining);
1204 operation = getRedirectOperation(operation, httpStatus);
1205
1206 if (const QNetworkCookieJar *const cookieJar = manager->cookieJar()) {
1207 auto cookies = cookieJar->cookiesForUrl(url);
1208 if (!cookies.empty()) {
1209 redirectRequest.setHeader(QNetworkRequest::KnownHeaders::CookieHeader,
1210 QVariant::fromValue(cookies));
1211 }
1212 }
1213
1214 if (httpRequest.redirectPolicy() != QNetworkRequest::UserVerifiedRedirectPolicy)
1215 followRedirect();
1216
1217 emit q->redirected(url);
1218}
1219
1220void QNetworkReplyHttpImplPrivate::followRedirect()
1221{
1222 Q_Q(QNetworkReplyHttpImpl);
1223 Q_ASSERT(managerPrivate);
1224
1225 rawHeaders.clear();
1226 cookedHeaders.clear();
1227
1228 if (managerPrivate->thread)
1229 managerPrivate->thread->disconnect();
1230
1231#if QT_CONFIG(bearermanagement)
1232 // If the original request didn't need a session (i.e. it was to localhost)
1233 // then we might not have a session open, to which to redirect, if the
1234 // new URL is remote. When this happens, we need to open the session now:
1235 if (isSessionNeeded(url)) {
1236 if (auto session = managerPrivate->getNetworkSession()) {
1237 if (session->state() != QNetworkSession::State::Connected || !session->isOpen()) {
1238 startWaitForSession(session);
1239 // Need to set 'request' to the redirectRequest so that when QNAM restarts
1240 // the request after the session starts it will not repeat the previous request.
1241 request = redirectRequest;
1242 // Return now, QNAM will start the request when the session has started.
1243 return;
1244 }
1245 }
1246 }
1247#endif // bearer management
1248
1249 QMetaObject::invokeMethod(q, "start", Qt::QueuedConnection,
1250 Q_ARG(QNetworkRequest, redirectRequest));
1251}
1252
1253void QNetworkReplyHttpImplPrivate::checkForRedirect(const int statusCode)
1254{
1255 Q_Q(QNetworkReplyHttpImpl);
1256 switch (statusCode) {
1257 case 301: // Moved Permanently
1258 case 302: // Found
1259 case 303: // See Other
1260 case 307: // Temporary Redirect
1261 case 308: // Permanent Redirect
1262 // What do we do about the caching of the HTML note?
1263 // The response to a 303 MUST NOT be cached, while the response to
1264 // all of the others is cacheable if the headers indicate it to be
1265 QByteArray header = q->rawHeader("location");
1266 QUrl url = QUrl(QString::fromUtf8(header));
1267 if (!url.isValid())
1268 url = QUrl(QLatin1String(header));
1269 q->setAttribute(QNetworkRequest::RedirectionTargetAttribute, url);
1270 }
1271}
1272
1273void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QList<QPair<QByteArray,QByteArray> > &hm,
1274 int sc, const QString &rp, bool pu,
1275 QSharedPointer<char> db,
1276 qint64 contentLength,
1277 qint64 removedContentLength,
1278 bool spdyWasUsed)
1279{
1280 Q_Q(QNetworkReplyHttpImpl);
1281 Q_UNUSED(contentLength);
1282
1283 statusCode = sc;
1284 reasonPhrase = rp;
1285
1286#ifndef QT_NO_SSL
1287 // We parse this header only if we're using secure transport:
1288 //
1289 // RFC6797, 8.1
1290 // If an HTTP response is received over insecure transport, the UA MUST
1291 // ignore any present STS header field(s).
1292 if (url.scheme() == QLatin1String("https") && managerPrivate->stsEnabled)
1293 managerPrivate->stsCache.updateFromHeaders(hm, url);
1294#endif
1295 // Download buffer
1296 if (!db.isNull()) {
1297 downloadBufferPointer = db;
1298 downloadZerocopyBuffer = downloadBufferPointer.data();
1299 downloadBufferCurrentSize = 0;
1300 q->setAttribute(QNetworkRequest::DownloadBufferAttribute, QVariant::fromValue<QSharedPointer<char> > (downloadBufferPointer));
1301 }
1302
1303 q->setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, pu);
1304 const QVariant http2Allowed = request.attribute(QNetworkRequest::HTTP2AllowedAttribute);
1305 const QVariant http2Direct = request.attribute(QNetworkRequest::Http2DirectAttribute);
1306 if ((http2Allowed.isValid() && http2Allowed.toBool())
1307 || (http2Direct.isValid() && http2Direct.toBool())) {
1308 q->setAttribute(QNetworkRequest::HTTP2WasUsedAttribute, spdyWasUsed);
1309 q->setAttribute(QNetworkRequest::SpdyWasUsedAttribute, false);
1310 } else {
1311 q->setAttribute(QNetworkRequest::SpdyWasUsedAttribute, spdyWasUsed);
1312 q->setAttribute(QNetworkRequest::HTTP2WasUsedAttribute, false);
1313 }
1314
1315 // reconstruct the HTTP header
1316 QList<QPair<QByteArray, QByteArray> > headerMap = hm;
1317 QList<QPair<QByteArray, QByteArray> >::ConstIterator it = headerMap.constBegin(),
1318 end = headerMap.constEnd();
1319 for (; it != end; ++it) {
1320 QByteArray value = q->rawHeader(it->first);
1321
1322 // Reset any previous "location" header set in the reply. In case of
1323 // redirects, we don't want to 'append' multiple location header values,
1324 // rather we keep only the latest one
1325 if (it->first.toLower() == "location")
1326 value.clear();
1327
1328 if (!value.isEmpty()) {
1329 // Why are we appending values for headers which are already
1330 // present?
1331 if (it->first.compare("set-cookie", Qt::CaseInsensitive) == 0)
1332 value += '\n';
1333 else
1334 value += ", ";
1335 }
1336 value += it->second;
1337 q->setRawHeader(it->first, value);
1338 }
1339
1340 q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1341 q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
1342 if (removedContentLength != -1)
1343 q->setAttribute(QNetworkRequest::OriginalContentLengthAttribute, removedContentLength);
1344
1345 // is it a redirection?
1346 if (!isHttpRedirectResponse())
1347 checkForRedirect(statusCode);
1348
1349 if (statusCode >= 500 && statusCode < 600) {
1350 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1351 if (nc) {
1352 QNetworkCacheMetaData metaData = nc->metaData(httpRequest.url());
1353 QNetworkHeadersPrivate cacheHeaders;
1354 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
1355 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
1356 it = cacheHeaders.findRawHeader("Cache-Control");
1357 bool mustReValidate = false;
1358 if (it != cacheHeaders.rawHeaders.constEnd()) {
1359 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
1360 if (cacheControl.contains("must-revalidate"))
1361 mustReValidate = true;
1362 }
1363 if (!mustReValidate && sendCacheContents(metaData))
1364 return;
1365 }
1366 }
1367
1368 if (statusCode == 304) {
1369#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1370 qDebug() << "Received a 304 from" << request.url();
1371#endif
1372 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1373 if (nc) {
1374 QNetworkCacheMetaData oldMetaData = nc->metaData(httpRequest.url());
1375 QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData);
1376 if (oldMetaData != metaData)
1377 nc->updateMetaData(metaData);
1378 if (sendCacheContents(metaData))
1379 return;
1380 }
1381 }
1382
1383
1384 if (statusCode != 304 && statusCode != 303) {
1385 if (!isCachingEnabled())
1386 setCachingEnabled(true);
1387 }
1388
1389 _q_metaDataChanged();
1390}
1391
1392void QNetworkReplyHttpImplPrivate::replyDownloadProgressSlot(qint64 bytesReceived, qint64 bytesTotal)
1393{
1394 Q_Q(QNetworkReplyHttpImpl);
1395
1396 // If we're closed just ignore this data
1397 if (!q->isOpen())
1398 return;
1399
1400 // we can be sure here that there is a download buffer
1401
1402 int pendingSignals = (int)pendingDownloadProgressEmissions->fetchAndAddAcquire(-1) - 1;
1403 if (pendingSignals > 0) {
1404 // Let's ignore this signal and look at the next one coming in
1405 // (signal comppression)
1406 return;
1407 }
1408
1409 if (!q->isOpen())
1410 return;
1411
1412 if (cacheEnabled && isCachingAllowed() && bytesReceived == bytesTotal) {
1413 // Write everything in one go if we use a download buffer. might be more performant.
1414 initCacheSaveDevice();
1415 // need to check again if cache enabled and device exists
1416 if (cacheSaveDevice && cacheEnabled)
1417 cacheSaveDevice->write(downloadZerocopyBuffer, bytesTotal);
1418 // FIXME where is it closed?
1419 }
1420
1421 if (isHttpRedirectResponse())
1422 return;
1423
1424 bytesDownloaded = bytesReceived;
1425
1426 downloadBufferCurrentSize = bytesReceived;
1427
1428 // Only emit readyRead when actual data is there
1429 // emit readyRead before downloadProgress incase this will cause events to be
1430 // processed and we get into a recursive call (as in QProgressDialog).
1431 if (bytesDownloaded > 0)
1432 emit q->readyRead();
1433 if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
1434 downloadProgressSignalChoke.restart();
1435 emit q->downloadProgress(bytesDownloaded, bytesTotal);
1436 }
1437}
1438
1439void QNetworkReplyHttpImplPrivate::httpAuthenticationRequired(const QHttpNetworkRequest &request,
1440 QAuthenticator *auth)
1441{
1442 managerPrivate->authenticationRequired(auth, q_func(), synchronous, url, &urlForLastAuthentication, request.withCredentials());
1443}
1444
1445#ifndef QT_NO_NETWORKPROXY
1446void QNetworkReplyHttpImplPrivate::proxyAuthenticationRequired(const QNetworkProxy &proxy,
1447 QAuthenticator *authenticator)
1448{
1449 managerPrivate->proxyAuthenticationRequired(request.url(), proxy, synchronous, authenticator, &lastProxyAuthentication);
1450}
1451#endif
1452
1453void QNetworkReplyHttpImplPrivate::httpError(QNetworkReply::NetworkError errorCode,
1454 const QString &errorString)
1455{
1456#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1457 qDebug() << "http error!" << errorCode << errorString;
1458#endif
1459
1460 // FIXME?
1461 error(errorCode, errorString);
1462}
1463
1464#ifndef QT_NO_SSL
1465void QNetworkReplyHttpImplPrivate::replyEncrypted()
1466{
1467 Q_Q(QNetworkReplyHttpImpl);
1468 emit q->encrypted();
1469}
1470
1471void QNetworkReplyHttpImplPrivate::replySslErrors(
1472 const QList<QSslError> &list, bool *ignoreAll, QList<QSslError> *toBeIgnored)
1473{
1474 Q_Q(QNetworkReplyHttpImpl);
1475 emit q->sslErrors(list);
1476 // Check if the callback set any ignore and return this here to http thread
1477 if (pendingIgnoreAllSslErrors)
1478 *ignoreAll = true;
1479 if (!pendingIgnoreSslErrorsList.isEmpty())
1480 *toBeIgnored = pendingIgnoreSslErrorsList;
1481}
1482
1483void QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfiguration &newSslConfiguration)
1484{
1485 // Receiving the used SSL configuration from the HTTP thread
1486 if (sslConfiguration.data())
1487 *sslConfiguration = newSslConfiguration;
1488 else
1489 sslConfiguration.reset(new QSslConfiguration(newSslConfiguration));
1490}
1491
1492void QNetworkReplyHttpImplPrivate::replyPreSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator *authenticator)
1493{
1494 Q_Q(QNetworkReplyHttpImpl);
1495 emit q->preSharedKeyAuthenticationRequired(authenticator);
1496}
1497#endif
1498
1499// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1500void QNetworkReplyHttpImplPrivate::resetUploadDataSlot(bool *r)
1501{
1502 *r = uploadByteDevice->reset();
1503 if (*r) {
1504 // reset our own position which is used for the inter-thread communication
1505 uploadByteDevicePosition = 0;
1506 }
1507}
1508
1509// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1510void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 pos, qint64 amount)
1511{
1512 if (uploadByteDevicePosition + amount != pos) {
1513 // Sanity check, should not happen.
1514 error(QNetworkReply::UnknownNetworkError, QString());
1515 return;
1516 }
1517 uploadByteDevice->advanceReadPointer(amount);
1518 uploadByteDevicePosition += amount;
1519}
1520
1521// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1522void QNetworkReplyHttpImplPrivate::wantUploadDataSlot(qint64 maxSize)
1523{
1524 Q_Q(QNetworkReplyHttpImpl);
1525
1526 // call readPointer
1527 qint64 currentUploadDataLength = 0;
1528 char *data = const_cast<char*>(uploadByteDevice->readPointer(maxSize, currentUploadDataLength));
1529
1530 if (currentUploadDataLength == 0) {
1531 uploadDeviceChoking = true;
1532 // No bytes from upload byte device. There will be bytes later, it will emit readyRead()
1533 // and our uploadByteDeviceReadyReadSlot() is called.
1534 return;
1535 } else {
1536 uploadDeviceChoking = false;
1537 }
1538
1539 // Let's make a copy of this data
1540 QByteArray dataArray(data, currentUploadDataLength);
1541
1542 // Communicate back to HTTP thread
1543 emit q->haveUploadData(uploadByteDevicePosition, dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
1544}
1545
1546void QNetworkReplyHttpImplPrivate::uploadByteDeviceReadyReadSlot()
1547{
1548 // Start the flow between this thread and the HTTP thread again by triggering a upload.
1549 // However only do this when we were choking before, else the state in
1550 // QNonContiguousByteDeviceThreadForwardImpl gets messed up.
1551 if (uploadDeviceChoking) {
1552 uploadDeviceChoking = false;
1553 wantUploadDataSlot(1024);
1554 }
1555}
1556
1557
1558/*
1559 A simple web page that can be used to test us: http://www.procata.com/cachetest/
1560 */
1561bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData &metaData)
1562{
1563 Q_Q(QNetworkReplyHttpImpl);
1564
1565 setCachingEnabled(false);
1566 if (!metaData.isValid())
1567 return false;
1568
1569 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1570 Q_ASSERT(nc);
1571 QIODevice *contents = nc->data(url);
1572 if (!contents) {
1573#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1574 qDebug() << "Cannot send cache, the contents are 0" << url;
1575#endif
1576 return false;
1577 }
1578 contents->setParent(q);
1579
1580 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
1581 int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt();
1582 if (status < 100)
1583 status = 200; // fake it
1584
1585 statusCode = status;
1586
1587 q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
1588 q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
1589 q->setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
1590
1591 QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders();
1592 QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(),
1593 end = rawHeaders.constEnd();
1594 QUrl redirectUrl;
1595 for ( ; it != end; ++it) {
1596 if (httpRequest.isFollowRedirects() &&
1597 !it->first.compare("location", Qt::CaseInsensitive))
1598 redirectUrl = QUrl::fromEncoded(it->second);
1599 setRawHeader(it->first, it->second);
1600 }
1601
1602 if (!isHttpRedirectResponse())
1603 checkForRedirect(status);
1604
1605 cacheLoadDevice = contents;
1606 q->connect(cacheLoadDevice, SIGNAL(readyRead()), SLOT(_q_cacheLoadReadyRead()));
1607 q->connect(cacheLoadDevice, SIGNAL(readChannelFinished()), SLOT(_q_cacheLoadReadyRead()));
1608
1609 // This needs to be emitted in the event loop because it can be reached at
1610 // the direct code path of qnam.get(...) before the user has a chance
1611 // to connect any signals.
1612 QMetaObject::invokeMethod(q, "_q_metaDataChanged", Qt::QueuedConnection);
1613 QMetaObject::invokeMethod(q, "_q_cacheLoadReadyRead", Qt::QueuedConnection);
1614
1615
1616#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1617 qDebug() << "Successfully sent cache:" << url << contents->size() << "bytes";
1618#endif
1619
1620 // Do redirect processing
1621 if (httpRequest.isFollowRedirects() && QHttpNetworkReply::isHttpRedirect(status)) {
1622 QMetaObject::invokeMethod(q, "onRedirected", Qt::QueuedConnection,
1623 Q_ARG(QUrl, redirectUrl),
1624 Q_ARG(int, status),
1625 Q_ARG(int, httpRequest.redirectCount() - 1));
1626 }
1627
1628 // Set the following flag so we can ignore some signals from HTTP thread
1629 // that would still come
1630 loadingFromCache = true;
1631 return true;
1632}
1633
1634QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const
1635{
1636 Q_Q(const QNetworkReplyHttpImpl);
1637
1638 QNetworkCacheMetaData metaData = oldMetaData;
1639
1640 QNetworkHeadersPrivate cacheHeaders;
1641 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
1642 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
1643
1644 const QList<QByteArray> newHeaders = q->rawHeaderList();
1645 for (QByteArray header : newHeaders) {
1646 QByteArray originalHeader = header;
1647 header = header.toLower();
1648 bool hop_by_hop =
1649 (header == "connection"
1650 || header == "keep-alive"
1651 || header == "proxy-authenticate"
1652 || header == "proxy-authorization"
1653 || header == "te"
1654 || header == "trailers"
1655 || header == "transfer-encoding"
1656 || header == "upgrade");
1657 if (hop_by_hop)
1658 continue;
1659
1660 if (header == "set-cookie")
1661 continue;
1662
1663 // for 4.6.0, we were planning to not store the date header in the
1664 // cached resource; through that we planned to reduce the number
1665 // of writes to disk when using a QNetworkDiskCache (i.e. don't
1666 // write to disk when only the date changes).
1667 // However, without the date we cannot calculate the age of the page
1668 // anymore.
1669 //if (header == "date")
1670 //continue;
1671
1672 // Don't store Warning 1xx headers
1673 if (header == "warning") {
1674 QByteArray v = q->rawHeader(header);
1675 if (v.length() == 3
1676 && v[0] == '1'
1677 && v[1] >= '0' && v[1] <= '9'
1678 && v[2] >= '0' && v[2] <= '9')
1679 continue;
1680 }
1681
1682 it = cacheHeaders.findRawHeader(header);
1683 if (it != cacheHeaders.rawHeaders.constEnd()) {
1684 // Match the behavior of Firefox and assume Cache-Control: "no-transform"
1685 if (header == "content-encoding"
1686 || header == "content-range"
1687 || header == "content-type")
1688 continue;
1689 }
1690
1691 // IIS has been known to send "Content-Length: 0" on 304 responses, so
1692 // ignore this too
1693 if (header == "content-length" && statusCode == 304)
1694 continue;
1695
1696#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1697 QByteArray n = q->rawHeader(header);
1698 QByteArray o;
1699 if (it != cacheHeaders.rawHeaders.constEnd())
1700 o = (*it).second;
1701 if (n != o && header != "date") {
1702 qDebug() << "replacing" << header;
1703 qDebug() << "new" << n;
1704 qDebug() << "old" << o;
1705 }
1706#endif
1707 cacheHeaders.setRawHeader(originalHeader, q->rawHeader(header));
1708 }
1709 metaData.setRawHeaders(cacheHeaders.rawHeaders);
1710
1711 bool checkExpired = true;
1712
1713 QHash<QByteArray, QByteArray> cacheControl;
1714 it = cacheHeaders.findRawHeader("Cache-Control");
1715 if (it != cacheHeaders.rawHeaders.constEnd()) {
1716 cacheControl = parseHttpOptionHeader(it->second);
1717 QByteArray maxAge = cacheControl.value("max-age");
1718 if (!maxAge.isEmpty()) {
1719 checkExpired = false;
1720 QDateTime dt = QDateTime::currentDateTimeUtc();
1721 dt = dt.addSecs(maxAge.toInt());
1722 metaData.setExpirationDate(dt);
1723 }
1724 }
1725 if (checkExpired) {
1726 it = cacheHeaders.findRawHeader("expires");
1727 if (it != cacheHeaders.rawHeaders.constEnd()) {
1728 QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(it->second);
1729 metaData.setExpirationDate(expiredDateTime);
1730 }
1731 }
1732
1733 it = cacheHeaders.findRawHeader("last-modified");
1734 if (it != cacheHeaders.rawHeaders.constEnd())
1735 metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(it->second));
1736
1737 bool canDiskCache;
1738 // only cache GET replies by default, all other replies (POST, PUT, DELETE)
1739 // are not cacheable by default (according to RFC 2616 section 9)
1740 if (httpRequest.operation() == QHttpNetworkRequest::Get) {
1741
1742 canDiskCache = true;
1743 // HTTP/1.1. Check the Cache-Control header
1744 if (cacheControl.contains("no-store"))
1745 canDiskCache = false;
1746
1747 // responses to POST might be cacheable
1748 } else if (httpRequest.operation() == QHttpNetworkRequest::Post) {
1749
1750 canDiskCache = false;
1751 // some pages contain "expires:" and "cache-control: no-cache" field,
1752 // so we only might cache POST requests if we get "cache-control: max-age ..."
1753 if (cacheControl.contains("max-age"))
1754 canDiskCache = true;
1755
1756 // responses to PUT and DELETE are not cacheable
1757 } else {
1758 canDiskCache = false;
1759 }
1760
1761 metaData.setSaveToDisk(canDiskCache);
1762 QNetworkCacheMetaData::AttributesMap attributes;
1763 if (statusCode != 304) {
1764 // update the status code
1765 attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1766 attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
1767 } else {
1768 // this is a redirection, keep the attributes intact
1769 attributes = oldMetaData.attributes();
1770 }
1771 metaData.setAttributes(attributes);
1772 return metaData;
1773}
1774
1775bool QNetworkReplyHttpImplPrivate::canResume() const
1776{
1777 Q_Q(const QNetworkReplyHttpImpl);
1778
1779 // Only GET operation supports resuming.
1780 if (operation != QNetworkAccessManager::GetOperation)
1781 return false;
1782
1783 // Can only resume if server/resource supports Range header.
1784 QByteArray acceptRangesheaderName("Accept-Ranges");
1785 if (!q->hasRawHeader(acceptRangesheaderName) || q->rawHeader(acceptRangesheaderName) == "none")
1786 return false;
1787
1788 // We only support resuming for byte ranges.
1789 if (request.hasRawHeader("Range")) {
1790 QByteArray range = request.rawHeader("Range");
1791 if (!range.startsWith("bytes="))
1792 return false;
1793 }
1794
1795 // If we're using a download buffer then we don't support resuming/migration
1796 // right now. Too much trouble.
1797 if (downloadZerocopyBuffer)
1798 return false;
1799
1800 return true;
1801}
1802
1803void QNetworkReplyHttpImplPrivate::setResumeOffset(quint64 offset)
1804{
1805 resumeOffset = offset;
1806}
1807
1808/*!
1809 Starts the backend. Returns \c true if the backend is started. Returns \c false if the backend
1810 could not be started due to an unopened or roaming session. The caller should recall this
1811 function once the session has been opened or the roaming process has finished.
1812*/
1813bool QNetworkReplyHttpImplPrivate::start(const QNetworkRequest &newHttpRequest)
1814{
1815#ifndef QT_NO_BEARERMANAGEMENT
1816 QSharedPointer<QNetworkSession> networkSession(managerPrivate->getNetworkSession());
1817 if (!networkSession || QNetworkStatusMonitor::isEnabled()) {
1818#endif
1819 postRequest(newHttpRequest);
1820 return true;
1821#ifndef QT_NO_BEARERMANAGEMENT
1822 }
1823
1824 // This is not ideal.
1825 if (!isSessionNeeded(url)) {
1826 // Don't need to check for an open session if we don't need one.
1827 postRequest(newHttpRequest);
1828 return true;
1829 }
1830
1831 if (networkSession->isOpen() &&
1832 networkSession->state() == QNetworkSession::Connected) {
1833 Q_Q(QNetworkReplyHttpImpl);
1834 QObject::connect(networkSession.data(), SIGNAL(usagePoliciesChanged(QNetworkSession::UsagePolicies)),
1835 q, SLOT(_q_networkSessionUsagePoliciesChanged(QNetworkSession::UsagePolicies)));
1836 postRequest(newHttpRequest);
1837 return true;
1838 } else if (synchronous) {
1839 // Command line applications using the synchronous path such as xmlpatterns may need an extra push.
1840 networkSession->open();
1841 if (networkSession->waitForOpened()) {
1842 postRequest(newHttpRequest);
1843 return true;
1844 }
1845 }
1846 return false;
1847#endif
1848}
1849
1850#if QT_CONFIG(bearermanagement)
1851bool QNetworkReplyHttpImplPrivate::startWaitForSession(QSharedPointer<QNetworkSession> &session)
1852{
1853 Q_Q(QNetworkReplyHttpImpl);
1854 state = WaitingForSession;
1855
1856 if (session) {
1857 QObject::connect(session.data(), SIGNAL(error(QNetworkSession::SessionError)),
1858 q, SLOT(_q_networkSessionFailed()), Qt::QueuedConnection);
1859
1860 if (!session->isOpen()) {
1861 QVariant isBackground = request.attribute(QNetworkRequest::BackgroundRequestAttribute,
1862 QVariant::fromValue(false));
1863 session->setSessionProperty(QStringLiteral("ConnectInBackground"), isBackground);
1864 session->open();
1865 }
1866 return true;
1867 }
1868 const Qt::ConnectionType connection = synchronous ? Qt::DirectConnection : Qt::QueuedConnection;
1869 qWarning("Backend is waiting for QNetworkSession to connect, but there is none!");
1870 QMetaObject::invokeMethod(q, "_q_error", connection,
1871 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::NetworkSessionFailedError),
1872 Q_ARG(QString, QCoreApplication::translate("QNetworkReply", "Network session error.")));
1873 QMetaObject::invokeMethod(q, "_q_finished", connection);
1874 return false;
1875}
1876#endif // QT_CONFIG(bearermanagement)
1877
1878void QNetworkReplyHttpImplPrivate::_q_startOperation()
1879{
1880 Q_Q(QNetworkReplyHttpImpl);
1881
1882 if (state == Working) // ensure this function is only being called once
1883 return;
1884
1885 state = Working;
1886
1887#ifndef QT_NO_BEARERMANAGEMENT
1888 // Do not start background requests if they are not allowed by session policy
1889 QSharedPointer<QNetworkSession> session(manager->d_func()->getNetworkSession());
1890 QVariant isBackground = request.attribute(QNetworkRequest::BackgroundRequestAttribute, QVariant::fromValue(false));
1891 if (isBackground.toBool() && session && session->usagePolicies().testFlag(QNetworkSession::NoBackgroundTrafficPolicy)) {
1892 QMetaObject::invokeMethod(q, "_q_error", synchronous ? Qt::DirectConnection : Qt::QueuedConnection,
1893 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::BackgroundRequestNotAllowedError),
1894 Q_ARG(QString, QCoreApplication::translate("QNetworkReply", "Background request not allowed.")));
1895 QMetaObject::invokeMethod(q, "_q_finished", synchronous ? Qt::DirectConnection : Qt::QueuedConnection);
1896 return;
1897 }
1898
1899 if (!start(request)) {
1900 // backend failed to start because the session state is not Connected.
1901 // QNetworkAccessManager will call reply->backend->start() again for us when the session
1902 // state changes.
1903 if (!startWaitForSession(session))
1904 return;
1905 } else if (session && !QNetworkStatusMonitor::isEnabled()) {
1906 QObject::connect(session.data(), SIGNAL(stateChanged(QNetworkSession::State)),
1907 q, SLOT(_q_networkSessionStateChanged(QNetworkSession::State)),
1908 Qt::QueuedConnection);
1909 }
1910#else
1911 if (!start(request)) {
1912 qWarning("Backend start failed");
1913 QMetaObject::invokeMethod(q, "_q_error", synchronous ? Qt::DirectConnection : Qt::QueuedConnection,
1914 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::UnknownNetworkError),
1915 Q_ARG(QString, QCoreApplication::translate("QNetworkReply", "backend start error.")));
1916 QMetaObject::invokeMethod(q, "_q_finished", synchronous ? Qt::DirectConnection : Qt::QueuedConnection);
1917 return;
1918 }
1919#endif // QT_NO_BEARERMANAGEMENT
1920
1921 if (synchronous) {
1922 state = Finished;
1923 q_func()->setFinished(true);
1924 }
1925}
1926
1927void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead()
1928{
1929 Q_Q(QNetworkReplyHttpImpl);
1930
1931 if (state != Working)
1932 return;
1933 if (!cacheLoadDevice || !q->isOpen() || !cacheLoadDevice->bytesAvailable())
1934 return;
1935
1936 // FIXME Optimize to use zerocopy download buffer if it is a QBuffer.
1937 // Needs to be done where sendCacheContents() (?) of HTTP is emitting
1938 // metaDataChanged ?
1939
1940
1941 QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
1942
1943 // emit readyRead before downloadProgress incase this will cause events to be
1944 // processed and we get into a recursive call (as in QProgressDialog).
1945
1946 if (!(isHttpRedirectResponse())) {
1947 // This readyRead() goes to the user. The user then may or may not read() anything.
1948 emit q->readyRead();
1949
1950 if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
1951 downloadProgressSignalChoke.restart();
1952 emit q->downloadProgress(bytesDownloaded,
1953 totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
1954 }
1955 }
1956
1957 // A signal we've emitted might be handled by a slot that aborts,
1958 // so we need to check for that and bail out if it's happened:
1959 if (!q->isOpen())
1960 return;
1961
1962 // If there are still bytes available in the cacheLoadDevice then the user did not read
1963 // in response to the readyRead() signal. This means we have to load from the cacheLoadDevice
1964 // and buffer that stuff. This is needed to be able to properly emit finished() later.
1965 while (cacheLoadDevice->bytesAvailable() && !isHttpRedirectResponse())
1966 buffer.append(cacheLoadDevice->readAll());
1967
1968 if (cacheLoadDevice->isSequential()) {
1969 // check if end and we can read the EOF -1
1970 char c;
1971 qint64 actualCount = cacheLoadDevice->read(&c, 1);
1972 if (actualCount < 0) {
1973 cacheLoadDevice->deleteLater();
1974 cacheLoadDevice = nullptr;
1975 QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
1976 } else if (actualCount == 1) {
1977 // This is most probably not happening since most QIODevice returned something proper for bytesAvailable()
1978 // and had already been "emptied".
1979 cacheLoadDevice->ungetChar(c);
1980 }
1981 } else if ((!cacheLoadDevice->isSequential() && cacheLoadDevice->atEnd())) {
1982 // This codepath is in case the cache device is a QBuffer, e.g. from QNetworkDiskCache.
1983 cacheLoadDevice->deleteLater();
1984 cacheLoadDevice = nullptr;
1985 QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
1986 }
1987}
1988
1989
1990void QNetworkReplyHttpImplPrivate::_q_bufferOutgoingDataFinished()
1991{
1992 Q_Q(QNetworkReplyHttpImpl);
1993
1994 // make sure this is only called once, ever.
1995 //_q_bufferOutgoingData may call it or the readChannelFinished emission
1996 if (state != Buffering)
1997 return;
1998
1999 // disconnect signals
2000 QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
2001 QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
2002
2003 // finally, start the request
2004 QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
2005}
2006
2007void QNetworkReplyHttpImplPrivate::_q_cacheSaveDeviceAboutToClose()
2008{
2009 // do not keep a dangling pointer to the device around (device
2010 // is closing because e.g. QAbstractNetworkCache::remove() was called).
2011 cacheSaveDevice = nullptr;
2012}
2013
2014void QNetworkReplyHttpImplPrivate::_q_bufferOutgoingData()
2015{
2016 Q_Q(QNetworkReplyHttpImpl);
2017
2018 if (!outgoingDataBuffer) {
2019 // first call, create our buffer
2020 outgoingDataBuffer = QSharedPointer<QRingBuffer>::create();
2021
2022 QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
2023 QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
2024 }
2025
2026 qint64 bytesBuffered = 0;
2027 qint64 bytesToBuffer = 0;
2028
2029 // read data into our buffer
2030 forever {
2031 bytesToBuffer = outgoingData->bytesAvailable();
2032 // unknown? just try 2 kB, this also ensures we always try to read the EOF
2033 if (bytesToBuffer <= 0)
2034 bytesToBuffer = 2*1024;
2035
2036 char *dst = outgoingDataBuffer->reserve(bytesToBuffer);
2037 bytesBuffered = outgoingData->read(dst, bytesToBuffer);
2038
2039 if (bytesBuffered == -1) {
2040 // EOF has been reached.
2041 outgoingDataBuffer->chop(bytesToBuffer);
2042
2043 _q_bufferOutgoingDataFinished();
2044 break;
2045 } else if (bytesBuffered == 0) {
2046 // nothing read right now, just wait until we get called again
2047 outgoingDataBuffer->chop(bytesToBuffer);
2048
2049 break;
2050 } else {
2051 // don't break, try to read() again
2052 outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered);
2053 }
2054 }
2055}
2056
2057#ifndef QT_NO_BEARERMANAGEMENT
2058void QNetworkReplyHttpImplPrivate::_q_networkSessionConnected()
2059{
2060 Q_Q(QNetworkReplyHttpImpl);
2061 Q_ASSERT(managerPrivate);
2062
2063 QSharedPointer<QNetworkSession> session = managerPrivate->getNetworkSession();
2064 if (!session)
2065 return;
2066
2067 if (session->state() != QNetworkSession::Connected)
2068 return;
2069
2070 switch (state) {
2071 case QNetworkReplyPrivate::Buffering:
2072 case QNetworkReplyPrivate::Working:
2073 case QNetworkReplyPrivate::Reconnecting:
2074 // Migrate existing downloads to new network connection.
2075 migrateBackend();
2076 break;
2077 case QNetworkReplyPrivate::WaitingForSession:
2078 // Start waiting requests.
2079 QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
2080 break;
2081 default:
2082 ;
2083 }
2084}
2085
2086void QNetworkReplyHttpImplPrivate::_q_networkSessionStateChanged(QNetworkSession::State sessionState)
2087{
2088 if (sessionState == QNetworkSession::Disconnected
2089 && state != Idle && state != Reconnecting) {
2090 error(QNetworkReplyImpl::NetworkSessionFailedError,
2091 QCoreApplication::translate("QNetworkReply", "Network session error."));
2092 finished();
2093 }
2094}
2095
2096void QNetworkReplyHttpImplPrivate::_q_networkSessionFailed()
2097{
2098 // Abort waiting and working replies.
2099 if (state == WaitingForSession || state == Working) {
2100 state = Working;
2101 QSharedPointer<QNetworkSession> session(manager->d_func()->getNetworkSession());
2102 QString errorStr;
2103 if (session)
2104 errorStr = session->errorString();
2105 else
2106 errorStr = QCoreApplication::translate("QNetworkReply", "Network session error.");
2107 error(QNetworkReplyImpl::NetworkSessionFailedError, errorStr);
2108 finished();
2109 }
2110}
2111
2112void QNetworkReplyHttpImplPrivate::_q_networkSessionUsagePoliciesChanged(QNetworkSession::UsagePolicies newPolicies)
2113{
2114 if (request.attribute(QNetworkRequest::BackgroundRequestAttribute).toBool()) {
2115 if (newPolicies & QNetworkSession::NoBackgroundTrafficPolicy) {
2116 // Abort waiting and working replies.
2117 if (state == WaitingForSession || state == Working) {
2118 state = Working;
2119 error(QNetworkReply::BackgroundRequestNotAllowedError,
2120 QCoreApplication::translate("QNetworkReply", "Background request not allowed."));
2121 finished();
2122 }
2123 // ### if canResume(), then we could resume automatically
2124 }
2125 }
2126
2127}
2128#endif
2129
2130
2131// need to have this function since the reply is a private member variable
2132// and the special backends need to access this.
2133void QNetworkReplyHttpImplPrivate::emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal)
2134{
2135 Q_Q(QNetworkReplyHttpImpl);
2136 if (isFinished)
2137 return;
2138
2139 if (!emitAllUploadProgressSignals) {
2140 //choke signal emissions, except the first and last signals which are unconditional
2141 if (uploadProgressSignalChoke.isValid()) {
2142 if (bytesSent != bytesTotal && uploadProgressSignalChoke.elapsed() < progressSignalInterval) {
2143 return;
2144 }
2145 uploadProgressSignalChoke.restart();
2146 } else {
2147 uploadProgressSignalChoke.start();
2148 }
2149 }
2150
2151 emit q->uploadProgress(bytesSent, bytesTotal);
2152}
2153
2154QNonContiguousByteDevice* QNetworkReplyHttpImplPrivate::createUploadByteDevice()
2155{
2156 Q_Q(QNetworkReplyHttpImpl);
2157
2158 if (outgoingDataBuffer)
2159 uploadByteDevice = QNonContiguousByteDeviceFactory::createShared(outgoingDataBuffer);
2160 else if (outgoingData) {
2161 uploadByteDevice = QNonContiguousByteDeviceFactory::createShared(outgoingData);
2162 } else {
2163 return nullptr;
2164 }
2165
2166 // We want signal emissions only for normal asynchronous uploads
2167 if (!synchronous)
2168 QObject::connect(uploadByteDevice.data(), SIGNAL(readProgress(qint64,qint64)),
2169 q, SLOT(emitReplyUploadProgress(qint64,qint64)));
2170
2171 return uploadByteDevice.data();
2172}
2173
2174void QNetworkReplyHttpImplPrivate::_q_finished()
2175{
2176 // This gets called queued, just forward to real call then
2177 finished();
2178}
2179
2180void QNetworkReplyHttpImplPrivate::finished()
2181{
2182 Q_Q(QNetworkReplyHttpImpl);
2183
2184 if (state == Finished || state == Aborted || state == WaitingForSession)
2185 return;
2186
2187 QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
2188 if (preMigrationDownloaded != Q_INT64_C(-1))
2189 totalSize = totalSize.toLongLong() + preMigrationDownloaded;
2190
2191#ifndef QT_NO_BEARERMANAGEMENT
2192 Q_ASSERT(managerPrivate);
2193 QSharedPointer<QNetworkSession> session = managerPrivate->getNetworkSession();
2194 if (!QNetworkStatusMonitor::isEnabled() && session && session->state() == QNetworkSession::Roaming &&
2195 state == Working && errorCode != QNetworkReply::OperationCanceledError) {
2196 // only content with a known size will fail with a temporary network failure error
2197 if (!totalSize.isNull()) {
2198 if (bytesDownloaded != totalSize) {
2199 if (migrateBackend()) {
2200 // either we are migrating or the request is finished/aborted
2201 if (state == Reconnecting || state == WaitingForSession) {
2202 return; // exit early if we are migrating.
2203 }
2204 } else {
2205 error(QNetworkReply::TemporaryNetworkFailureError,
2206 QNetworkReply::tr("Temporary network failure."));
2207 }
2208 }
2209 }
2210 }
2211#endif
2212
2213 // if we don't know the total size of or we received everything save the cache
2214 if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize)
2215 completeCacheSave();
2216
2217 // We check for errorCode too as in case of SSL handshake failure, we still
2218 // get the HTTP redirect status code (301, 303 etc)
2219 if (isHttpRedirectResponse() && errorCode == QNetworkReply::NoError)
2220 return;
2221
2222 state = Finished;
2223 q->setFinished(true);
2224
2225 if (totalSize.isNull() || totalSize == -1) {
2226 emit q->downloadProgress(bytesDownloaded, bytesDownloaded);
2227 } else {
2228 emit q->downloadProgress(bytesDownloaded, totalSize.toLongLong());
2229 }
2230
2231 if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
2232 emit q->uploadProgress(0, 0);
2233
2234 emit q->readChannelFinished();
2235 emit q->finished();
2236}
2237
2238void QNetworkReplyHttpImplPrivate::_q_error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
2239{
2240 this->error(code, errorMessage);
2241}
2242
2243
2244void QNetworkReplyHttpImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
2245{
2246 Q_Q(QNetworkReplyHttpImpl);
2247 // Can't set and emit multiple errors.
2248 if (errorCode != QNetworkReply::NoError) {
2249 qWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.");
2250 return;
2251 }
2252
2253 errorCode = code;
2254 q->setErrorString(errorMessage);
2255
2256 // note: might not be a good idea, since users could decide to delete us
2257 // which would delete the backend too...
2258 // maybe we should protect the backend
2259 emit q->error(code);
2260}
2261
2262void QNetworkReplyHttpImplPrivate::_q_metaDataChanged()
2263{
2264 // FIXME merge this with replyDownloadMetaData(); ?
2265
2266 Q_Q(QNetworkReplyHttpImpl);
2267 // 1. do we have cookies?
2268 // 2. are we allowed to set them?
2269 Q_ASSERT(manager);
2270 const auto it = cookedHeaders.constFind(QNetworkRequest::SetCookieHeader);
2271 if (it != cookedHeaders.cend()
2272 && request.attribute(QNetworkRequest::CookieSaveControlAttribute,
2273 QNetworkRequest::Automatic).toInt() == QNetworkRequest::Automatic) {
2274 QNetworkCookieJar *jar = manager->cookieJar();
2275 if (jar) {
2276 QList<QNetworkCookie> cookies =
2277 qvariant_cast<QList<QNetworkCookie> >(it.value());
2278 jar->setCookiesFromUrl(cookies, url);
2279 }
2280 }
2281 emit q->metaDataChanged();
2282}
2283
2284/*
2285 Migrates the backend of the QNetworkReply to a new network connection if required. Returns
2286 true if the reply is migrated or it is not required; otherwise returns \c false.
2287*/
2288bool QNetworkReplyHttpImplPrivate::migrateBackend()
2289{
2290 Q_Q(QNetworkReplyHttpImpl);
2291
2292 // Network reply is already finished or aborted, don't need to migrate.
2293 if (state == Finished || state == Aborted)
2294 return true;
2295
2296 // Backend does not support resuming download.
2297 if (!canResume())
2298 return false;
2299
2300 // Request has outgoing data, not migrating.
2301 if (outgoingData)
2302 return false;
2303
2304 // Request is serviced from the cache, don't need to migrate.
2305 if (cacheLoadDevice)
2306 return true;
2307
2308 state = Reconnecting;
2309
2310 cookedHeaders.clear();
2311 rawHeaders.clear();
2312
2313 preMigrationDownloaded = bytesDownloaded;
2314
2315 setResumeOffset(bytesDownloaded);
2316
2317 emit q->abortHttpRequest();
2318
2319 QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
2320
2321 return true;
2322}
2323
2324
2325void QNetworkReplyHttpImplPrivate::createCache()
2326{
2327 // check if we can save and if we're allowed to
2328 if (!managerPrivate->networkCache
2329 || !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool())
2330 return;
2331 cacheEnabled = true;
2332}
2333
2334bool QNetworkReplyHttpImplPrivate::isCachingEnabled() const
2335{
2336 return (cacheEnabled && managerPrivate->networkCache != nullptr);
2337}
2338
2339void QNetworkReplyHttpImplPrivate::setCachingEnabled(bool enable)
2340{
2341 if (!enable && !cacheEnabled)
2342 return; // nothing to do
2343 if (enable && cacheEnabled)
2344 return; // nothing to do either!
2345
2346 if (enable) {
2347 if (Q_UNLIKELY(bytesDownloaded)) {
2348 qDebug() << "setCachingEnabled: " << bytesDownloaded << " bytesDownloaded";
2349 // refuse to enable in this case
2350 qCritical("QNetworkReplyImpl: backend error: caching was enabled after some bytes had been written");
2351 return;
2352 }
2353
2354 createCache();
2355 } else {
2356 // someone told us to turn on, then back off?
2357 // ok... but you should make up your mind
2358 qDebug("QNetworkReplyImpl: setCachingEnabled(true) called after setCachingEnabled(false)");
2359 managerPrivate->networkCache->remove(url);
2360 cacheSaveDevice = nullptr;
2361 cacheEnabled = false;
2362 }
2363}
2364
2365bool QNetworkReplyHttpImplPrivate::isCachingAllowed() const
2366{
2367 return operation == QNetworkAccessManager::GetOperation || operation == QNetworkAccessManager::HeadOperation;
2368}
2369
2370void QNetworkReplyHttpImplPrivate::completeCacheSave()
2371{
2372 if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) {
2373 managerPrivate->networkCache->remove(url);
2374 } else if (cacheEnabled && cacheSaveDevice) {
2375 managerPrivate->networkCache->insert(cacheSaveDevice);
2376 }
2377 cacheSaveDevice = nullptr;
2378 cacheEnabled = false;
2379}
2380
2381QT_END_NAMESPACE
2382