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

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