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#include "qnetworkreplyimpl_p.h"
41#include "qnetworkaccessbackend_p.h"
42#include "qnetworkcookie.h"
43#include "qnetworkcookiejar.h"
44#include "qabstractnetworkcache.h"
45#include "QtCore/qcoreapplication.h"
46#include "QtCore/qdatetime.h"
47#include "QtNetwork/qsslconfiguration.h"
48#include "QtNetwork/qnetworksession.h" // ### Qt6: Remove include
49#include "qnetworkaccessmanager_p.h"
50
51#include <QtCore/QCoreApplication>
52
53QT_BEGIN_NAMESPACE
54
55inline QNetworkReplyImplPrivate::QNetworkReplyImplPrivate()
56 : backend(nullptr), outgoingData(nullptr),
57 copyDevice(nullptr),
58 cacheEnabled(false), cacheSaveDevice(nullptr),
59 notificationHandlingPaused(false),
60 bytesDownloaded(0), lastBytesDownloaded(-1), bytesUploaded(-1), preMigrationDownloaded(-1),
61 httpStatusCode(0),
62 state(Idle)
63 , downloadBufferReadPosition(0)
64 , downloadBufferCurrentSize(0)
65 , downloadBufferMaximumSize(0)
66 , downloadBuffer(nullptr)
67{
68 if (request.attribute(code: QNetworkRequest::EmitAllUploadProgressSignalsAttribute).toBool() == true)
69 emitAllUploadProgressSignals = true;
70}
71
72void QNetworkReplyImplPrivate::_q_startOperation()
73{
74 // ensure this function is only being called once
75 if (state == Working || state == Finished) {
76 qDebug() << "QNetworkReplyImpl::_q_startOperation was called more than once" << url;
77 return;
78 }
79 state = Working;
80
81 // note: if that method is called directly, it cannot happen that the backend is 0,
82 // because we just checked via a qobject_cast that we got a http backend (see
83 // QNetworkReplyImplPrivate::setup())
84 if (!backend) {
85 error(code: QNetworkReplyImpl::ProtocolUnknownError,
86 errorString: QCoreApplication::translate(context: "QNetworkReply", key: "Protocol \"%1\" is unknown").arg(a: url.scheme())); // not really true!;
87 finished();
88 return;
89 }
90
91#ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
92 Q_Q(QNetworkReplyImpl);
93 // Do not start background requests if they are not allowed by session policy
94 QSharedPointer<QNetworkSession> session(manager->d_func()->getNetworkSession());
95 QVariant isBackground = backend->request().attribute(code: QNetworkRequest::BackgroundRequestAttribute, defaultValue: QVariant::fromValue(value: false));
96 if (isBackground.toBool() && session && session->usagePolicies().testFlag(flag: QNetworkSession::NoBackgroundTrafficPolicy)) {
97 error(code: QNetworkReply::BackgroundRequestNotAllowedError,
98 errorString: QCoreApplication::translate(context: "QNetworkReply", key: "Background request not allowed."));
99 finished();
100 return;
101 }
102#endif
103
104 if (!backend->start()) {
105#ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
106 // backend failed to start because the session state is not Connected.
107 // QNetworkAccessManager will call _q_startOperation again for us when the session
108 // state changes.
109 state = WaitingForSession;
110
111 if (session) {
112 QObject::connect(sender: session.data(), SIGNAL(error(QNetworkSession::SessionError)),
113 receiver: q, SLOT(_q_networkSessionFailed()));
114
115 if (!session->isOpen()) {
116 session->setSessionProperty(QStringLiteral("ConnectInBackground"), value: isBackground);
117 session->open();
118 }
119 } else {
120 qWarning(msg: "Backend is waiting for QNetworkSession to connect, but there is none!");
121 state = Working;
122 error(code: QNetworkReplyImpl::NetworkSessionFailedError,
123 errorString: QCoreApplication::translate(context: "QNetworkReply", key: "Network session error."));
124 finished();
125 }
126#else
127 qWarning("Backend start failed");
128 state = Working;
129 error(QNetworkReplyImpl::UnknownNetworkError,
130 QCoreApplication::translate("QNetworkReply", "backend start error."));
131 finished();
132#endif
133 return;
134 } else {
135#ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
136 if (session) {
137 QObject::connect(sender: session.data(), SIGNAL(stateChanged(QNetworkSession::State)),
138 receiver: q, SLOT(_q_networkSessionStateChanged(QNetworkSession::State)), Qt::QueuedConnection);
139 }
140#endif
141 }
142
143#ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
144 if (session) {
145 //get notification of policy changes.
146 QObject::connect(sender: session.data(), SIGNAL(usagePoliciesChanged(QNetworkSession::UsagePolicies)),
147 receiver: q, SLOT(_q_networkSessionUsagePoliciesChanged(QNetworkSession::UsagePolicies)));
148 }
149#endif
150
151 // Prepare timer for progress notifications
152 downloadProgressSignalChoke.start();
153 uploadProgressSignalChoke.invalidate();
154
155 if (backend && backend->isSynchronous()) {
156 state = Finished;
157 q_func()->setFinished(true);
158 } else {
159 if (state != Finished) {
160 if (operation == QNetworkAccessManager::GetOperation)
161 pendingNotifications.push_back(x: NotifyDownstreamReadyWrite);
162
163 handleNotifications();
164 }
165 }
166}
167
168void QNetworkReplyImplPrivate::_q_copyReadyRead()
169{
170 Q_Q(QNetworkReplyImpl);
171 if (state != Working)
172 return;
173 if (!copyDevice || !q->isOpen())
174 return;
175
176 // FIXME Optimize to use download buffer if it is a QBuffer.
177 // Needs to be done where sendCacheContents() (?) of HTTP is emitting
178 // metaDataChanged ?
179
180 forever {
181 qint64 bytesToRead = nextDownstreamBlockSize();
182 if (bytesToRead == 0)
183 // we'll be called again, eventually
184 break;
185
186 bytesToRead = qBound<qint64>(min: 1, val: bytesToRead, max: copyDevice->bytesAvailable());
187 qint64 bytesActuallyRead = copyDevice->read(data: buffer.reserve(bytes: bytesToRead), maxlen: bytesToRead);
188 if (bytesActuallyRead == -1) {
189 buffer.chop(bytes: bytesToRead);
190 backendNotify(notification: NotifyCopyFinished);
191 break;
192 }
193 buffer.chop(bytes: bytesToRead - bytesActuallyRead);
194
195 if (!copyDevice->isSequential() && copyDevice->atEnd()) {
196 backendNotify(notification: NotifyCopyFinished);
197 bytesDownloaded += bytesActuallyRead;
198 break;
199 }
200
201 bytesDownloaded += bytesActuallyRead;
202 }
203
204 if (bytesDownloaded == lastBytesDownloaded) {
205 // we didn't read anything
206 return;
207 }
208
209 lastBytesDownloaded = bytesDownloaded;
210 QVariant totalSize = cookedHeaders.value(key: QNetworkRequest::ContentLengthHeader);
211 if (preMigrationDownloaded != Q_INT64_C(-1))
212 totalSize = totalSize.toLongLong() + preMigrationDownloaded;
213 pauseNotificationHandling();
214 // emit readyRead before downloadProgress incase this will cause events to be
215 // processed and we get into a recursive call (as in QProgressDialog).
216 emit q->readyRead();
217 if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
218 downloadProgressSignalChoke.restart();
219 emit q->downloadProgress(bytesReceived: bytesDownloaded,
220 bytesTotal: totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
221 }
222 resumeNotificationHandling();
223}
224
225void QNetworkReplyImplPrivate::_q_copyReadChannelFinished()
226{
227 _q_copyReadyRead();
228}
229
230void QNetworkReplyImplPrivate::_q_bufferOutgoingDataFinished()
231{
232 Q_Q(QNetworkReplyImpl);
233
234 // make sure this is only called once, ever.
235 //_q_bufferOutgoingData may call it or the readChannelFinished emission
236 if (state != Buffering)
237 return;
238
239 // disconnect signals
240 QObject::disconnect(sender: outgoingData, SIGNAL(readyRead()), receiver: q, SLOT(_q_bufferOutgoingData()));
241 QObject::disconnect(sender: outgoingData, SIGNAL(readChannelFinished()), receiver: q, SLOT(_q_bufferOutgoingDataFinished()));
242
243 // finally, start the request
244 QMetaObject::invokeMethod(obj: q, member: "_q_startOperation", type: Qt::QueuedConnection);
245}
246
247void QNetworkReplyImplPrivate::_q_bufferOutgoingData()
248{
249 Q_Q(QNetworkReplyImpl);
250
251 if (!outgoingDataBuffer) {
252 // first call, create our buffer
253 outgoingDataBuffer = QSharedPointer<QRingBuffer>::create();
254
255 QObject::connect(sender: outgoingData, SIGNAL(readyRead()), receiver: q, SLOT(_q_bufferOutgoingData()));
256 QObject::connect(sender: outgoingData, SIGNAL(readChannelFinished()), receiver: q, SLOT(_q_bufferOutgoingDataFinished()));
257 }
258
259 qint64 bytesBuffered = 0;
260 qint64 bytesToBuffer = 0;
261
262 // read data into our buffer
263 forever {
264 bytesToBuffer = outgoingData->bytesAvailable();
265 // unknown? just try 2 kB, this also ensures we always try to read the EOF
266 if (bytesToBuffer <= 0)
267 bytesToBuffer = 2*1024;
268
269 char *dst = outgoingDataBuffer->reserve(bytes: bytesToBuffer);
270 bytesBuffered = outgoingData->read(data: dst, maxlen: bytesToBuffer);
271
272 if (bytesBuffered == -1) {
273 // EOF has been reached.
274 outgoingDataBuffer->chop(bytes: bytesToBuffer);
275
276 _q_bufferOutgoingDataFinished();
277 break;
278 } else if (bytesBuffered == 0) {
279 // nothing read right now, just wait until we get called again
280 outgoingDataBuffer->chop(bytes: bytesToBuffer);
281
282 break;
283 } else {
284 // don't break, try to read() again
285 outgoingDataBuffer->chop(bytes: bytesToBuffer - bytesBuffered);
286 }
287 }
288}
289
290#ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
291void QNetworkReplyImplPrivate::_q_networkSessionConnected()
292{
293 Q_Q(QNetworkReplyImpl);
294
295 if (manager.isNull())
296 return;
297
298 QSharedPointer<QNetworkSession> session = manager->d_func()->getNetworkSession();
299 if (!session)
300 return;
301
302 if (session->state() != QNetworkSession::Connected)
303 return;
304
305 switch (state) {
306 case QNetworkReplyPrivate::Buffering:
307 case QNetworkReplyPrivate::Working:
308 case QNetworkReplyPrivate::Reconnecting:
309 // Migrate existing downloads to new network connection.
310 migrateBackend();
311 break;
312 case QNetworkReplyPrivate::WaitingForSession:
313 // Start waiting requests.
314 QMetaObject::invokeMethod(obj: q, member: "_q_startOperation", type: Qt::QueuedConnection);
315 break;
316 default:
317 ;
318 }
319}
320
321void QNetworkReplyImplPrivate::_q_networkSessionStateChanged(QNetworkSession::State sessionState)
322{
323 if (sessionState == QNetworkSession::Disconnected
324 && state != Idle && state != Reconnecting) {
325 error(code: QNetworkReplyImpl::NetworkSessionFailedError,
326 errorString: QCoreApplication::translate(context: "QNetworkReply", key: "Network session error."));
327 finished();
328 }
329}
330
331void QNetworkReplyImplPrivate::_q_networkSessionFailed()
332{
333 // Abort waiting and working replies.
334 if (state == WaitingForSession || state == Working) {
335 state = Working;
336 QSharedPointer<QNetworkSession> session(manager->d_func()->getNetworkSession());
337 QString errorStr;
338 if (session)
339 errorStr = session->errorString();
340 else
341 errorStr = QCoreApplication::translate(context: "QNetworkReply", key: "Network session error.");
342 error(code: QNetworkReplyImpl::NetworkSessionFailedError, errorString: errorStr);
343 finished();
344 }
345}
346
347void QNetworkReplyImplPrivate::_q_networkSessionUsagePoliciesChanged(QNetworkSession::UsagePolicies newPolicies)
348{
349 if (backend->request().attribute(code: QNetworkRequest::BackgroundRequestAttribute).toBool()) {
350 if (newPolicies & QNetworkSession::NoBackgroundTrafficPolicy) {
351 // Abort waiting and working replies.
352 if (state == WaitingForSession || state == Working) {
353 state = Working;
354 error(code: QNetworkReply::BackgroundRequestNotAllowedError,
355 errorString: QCoreApplication::translate(context: "QNetworkReply", key: "Background request not allowed."));
356 finished();
357 }
358 // ### if backend->canResume(), then we could resume automatically, however no backend supports resuming
359 }
360 }
361}
362#endif
363
364void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req,
365 QIODevice *data)
366{
367 Q_Q(QNetworkReplyImpl);
368
369 outgoingData = data;
370 request = req;
371 originalRequest = req;
372 url = request.url();
373 operation = op;
374
375 q->QIODevice::open(mode: QIODevice::ReadOnly);
376 // Internal code that does a HTTP reply for the synchronous Ajax
377 // in Qt WebKit.
378 QVariant synchronousHttpAttribute = req.attribute(
379 code: static_cast<QNetworkRequest::Attribute>(QNetworkRequest::SynchronousRequestAttribute));
380 // The synchronous HTTP is a corner case, we will put all upload data in one big QByteArray in the outgoingDataBuffer.
381 // Yes, this is not the most efficient thing to do, but on the other hand synchronous XHR needs to die anyway.
382 if (synchronousHttpAttribute.toBool() && outgoingData) {
383 outgoingDataBuffer = QSharedPointer<QRingBuffer>::create();
384 qint64 previousDataSize = 0;
385 do {
386 previousDataSize = outgoingDataBuffer->size();
387 outgoingDataBuffer->append(qba: outgoingData->readAll());
388 } while (outgoingDataBuffer->size() != previousDataSize);
389 }
390
391 if (backend)
392 backend->setSynchronous(synchronousHttpAttribute.toBool());
393
394
395 if (outgoingData && backend && !backend->isSynchronous()) {
396 // there is data to be uploaded, e.g. HTTP POST.
397
398 if (!backend->needsResetableUploadData() || !outgoingData->isSequential()) {
399 // backend does not need upload buffering or
400 // fixed size non-sequential
401 // just start the operation
402 QMetaObject::invokeMethod(obj: q, member: "_q_startOperation", type: Qt::QueuedConnection);
403 } else {
404 bool bufferingDisallowed =
405 req.attribute(code: QNetworkRequest::DoNotBufferUploadDataAttribute,
406 defaultValue: false).toBool();
407
408 if (bufferingDisallowed) {
409 // if a valid content-length header for the request was supplied, we can disable buffering
410 // if not, we will buffer anyway
411 if (req.header(header: QNetworkRequest::ContentLengthHeader).isValid()) {
412 QMetaObject::invokeMethod(obj: q, member: "_q_startOperation", type: Qt::QueuedConnection);
413 } else {
414 state = Buffering;
415 QMetaObject::invokeMethod(obj: q, member: "_q_bufferOutgoingData", type: Qt::QueuedConnection);
416 }
417 } else {
418 // _q_startOperation will be called when the buffering has finished.
419 state = Buffering;
420 QMetaObject::invokeMethod(obj: q, member: "_q_bufferOutgoingData", type: Qt::QueuedConnection);
421 }
422 }
423 } else {
424 // for HTTP, we want to send out the request as fast as possible to the network, without
425 // invoking methods in a QueuedConnection
426 if (backend && backend->isSynchronous())
427 _q_startOperation();
428 else
429 QMetaObject::invokeMethod(obj: q, member: "_q_startOperation", type: Qt::QueuedConnection);
430 }
431}
432
433void QNetworkReplyImplPrivate::backendNotify(InternalNotifications notification)
434{
435 Q_Q(QNetworkReplyImpl);
436 const auto it = std::find(first: pendingNotifications.cbegin(), last: pendingNotifications.cend(), val: notification);
437 if (it == pendingNotifications.cend())
438 pendingNotifications.push_back(x: notification);
439
440 if (pendingNotifications.size() == 1)
441 QCoreApplication::postEvent(receiver: q, event: new QEvent(QEvent::NetworkReplyUpdated));
442}
443
444void QNetworkReplyImplPrivate::handleNotifications()
445{
446 if (notificationHandlingPaused)
447 return;
448
449 for (InternalNotifications notification : qExchange(t&: pendingNotifications, newValue: {})) {
450 if (state != Working)
451 return;
452 switch (notification) {
453 case NotifyDownstreamReadyWrite:
454 if (copyDevice)
455 _q_copyReadyRead();
456 else
457 backend->downstreamReadyWrite();
458 break;
459
460 case NotifyCloseDownstreamChannel:
461 backend->closeDownstreamChannel();
462 break;
463
464 case NotifyCopyFinished: {
465 QIODevice *dev = qExchange(t&: copyDevice, newValue: nullptr);
466 backend->copyFinished(dev);
467 break;
468 }
469 }
470 }
471}
472
473// Do not handle the notifications while we are emitting downloadProgress
474// or readyRead
475void QNetworkReplyImplPrivate::pauseNotificationHandling()
476{
477 notificationHandlingPaused = true;
478}
479
480// Resume notification handling
481void QNetworkReplyImplPrivate::resumeNotificationHandling()
482{
483 Q_Q(QNetworkReplyImpl);
484 notificationHandlingPaused = false;
485 if (pendingNotifications.size() >= 1)
486 QCoreApplication::postEvent(receiver: q, event: new QEvent(QEvent::NetworkReplyUpdated));
487}
488
489QAbstractNetworkCache *QNetworkReplyImplPrivate::networkCache() const
490{
491 if (!backend)
492 return nullptr;
493 return backend->networkCache();
494}
495
496void QNetworkReplyImplPrivate::createCache()
497{
498 // check if we can save and if we're allowed to
499 if (!networkCache()
500 || !request.attribute(code: QNetworkRequest::CacheSaveControlAttribute, defaultValue: true).toBool())
501 return;
502 cacheEnabled = true;
503}
504
505bool QNetworkReplyImplPrivate::isCachingEnabled() const
506{
507 return (cacheEnabled && networkCache() != nullptr);
508}
509
510void QNetworkReplyImplPrivate::setCachingEnabled(bool enable)
511{
512 if (!enable && !cacheEnabled)
513 return; // nothing to do
514 if (enable && cacheEnabled)
515 return; // nothing to do either!
516
517 if (enable) {
518 if (Q_UNLIKELY(bytesDownloaded)) {
519 // refuse to enable in this case
520 qCritical(msg: "QNetworkReplyImpl: backend error: caching was enabled after some bytes had been written");
521 return;
522 }
523
524 createCache();
525 } else {
526 // someone told us to turn on, then back off?
527 // ok... but you should make up your mind
528 qDebug(msg: "QNetworkReplyImpl: setCachingEnabled(true) called after setCachingEnabled(false) -- "
529 "backend %s probably needs to be fixed",
530 backend->metaObject()->className());
531 networkCache()->remove(url);
532 cacheSaveDevice = nullptr;
533 cacheEnabled = false;
534 }
535}
536
537void QNetworkReplyImplPrivate::completeCacheSave()
538{
539 if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) {
540 networkCache()->remove(url);
541 } else if (cacheEnabled && cacheSaveDevice) {
542 networkCache()->insert(device: cacheSaveDevice);
543 }
544 cacheSaveDevice = nullptr;
545 cacheEnabled = false;
546}
547
548void QNetworkReplyImplPrivate::emitUploadProgress(qint64 bytesSent, qint64 bytesTotal)
549{
550 Q_Q(QNetworkReplyImpl);
551 bytesUploaded = bytesSent;
552
553 if (!emitAllUploadProgressSignals) {
554 //choke signal emissions, except the first and last signals which are unconditional
555 if (uploadProgressSignalChoke.isValid()) {
556 if (bytesSent != bytesTotal && uploadProgressSignalChoke.elapsed() < progressSignalInterval) {
557 return;
558 }
559 uploadProgressSignalChoke.restart();
560 } else {
561 uploadProgressSignalChoke.start();
562 }
563 }
564
565 pauseNotificationHandling();
566 emit q->uploadProgress(bytesSent, bytesTotal);
567 resumeNotificationHandling();
568}
569
570
571qint64 QNetworkReplyImplPrivate::nextDownstreamBlockSize() const
572{
573 enum { DesiredBufferSize = 32 * 1024 };
574 if (readBufferMaxSize == 0)
575 return DesiredBufferSize;
576
577 return qMax<qint64>(a: 0, b: readBufferMaxSize - buffer.size());
578}
579
580void QNetworkReplyImplPrivate::initCacheSaveDevice()
581{
582 Q_Q(QNetworkReplyImpl);
583
584 // The disk cache does not support partial content, so don't even try to
585 // save any such content into the cache.
586 if (q->attribute(code: QNetworkRequest::HttpStatusCodeAttribute).toInt() == 206) {
587 cacheEnabled = false;
588 return;
589 }
590
591 // save the meta data
592 QNetworkCacheMetaData metaData;
593 metaData.setUrl(url);
594 metaData = backend->fetchCacheMetaData(metaData);
595
596 // save the redirect request also in the cache
597 QVariant redirectionTarget = q->attribute(code: QNetworkRequest::RedirectionTargetAttribute);
598 if (redirectionTarget.isValid()) {
599 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
600 attributes.insert(key: QNetworkRequest::RedirectionTargetAttribute, value: redirectionTarget);
601 metaData.setAttributes(attributes);
602 }
603
604 cacheSaveDevice = networkCache()->prepare(metaData);
605
606 if (!cacheSaveDevice || (cacheSaveDevice && !cacheSaveDevice->isOpen())) {
607 if (Q_UNLIKELY(cacheSaveDevice && !cacheSaveDevice->isOpen()))
608 qCritical(msg: "QNetworkReplyImpl: network cache returned a device that is not open -- "
609 "class %s probably needs to be fixed",
610 networkCache()->metaObject()->className());
611
612 networkCache()->remove(url);
613 cacheSaveDevice = nullptr;
614 cacheEnabled = false;
615 }
616}
617
618// we received downstream data and send this to the cache
619// and to our buffer (which in turn gets read by the user of QNetworkReply)
620void QNetworkReplyImplPrivate::appendDownstreamData(QByteDataBuffer &data)
621{
622 Q_Q(QNetworkReplyImpl);
623 if (!q->isOpen())
624 return;
625
626 if (cacheEnabled && !cacheSaveDevice) {
627 initCacheSaveDevice();
628 }
629
630 qint64 bytesWritten = 0;
631 for (int i = 0; i < data.bufferCount(); i++) {
632 QByteArray const &item = data[i];
633
634 if (cacheSaveDevice)
635 cacheSaveDevice->write(data: item.constData(), len: item.size());
636 buffer.append(qba: item);
637
638 bytesWritten += item.size();
639 }
640 data.clear();
641
642 bytesDownloaded += bytesWritten;
643 lastBytesDownloaded = bytesDownloaded;
644
645 appendDownstreamDataSignalEmissions();
646}
647
648void QNetworkReplyImplPrivate::appendDownstreamDataSignalEmissions()
649{
650 Q_Q(QNetworkReplyImpl);
651
652 QVariant totalSize = cookedHeaders.value(key: QNetworkRequest::ContentLengthHeader);
653 if (preMigrationDownloaded != Q_INT64_C(-1))
654 totalSize = totalSize.toLongLong() + preMigrationDownloaded;
655 pauseNotificationHandling();
656 // important: At the point of this readyRead(), the data parameter list must be empty,
657 // else implicit sharing will trigger memcpy when the user is reading data!
658 emit q->readyRead();
659 // emit readyRead before downloadProgress incase this will cause events to be
660 // processed and we get into a recursive call (as in QProgressDialog).
661 if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
662 downloadProgressSignalChoke.restart();
663 emit q->downloadProgress(bytesReceived: bytesDownloaded,
664 bytesTotal: totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
665 }
666
667 resumeNotificationHandling();
668 // do we still have room in the buffer?
669 if (nextDownstreamBlockSize() > 0)
670 backendNotify(notification: QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
671}
672
673// this is used when it was fetched from the cache, right?
674void QNetworkReplyImplPrivate::appendDownstreamData(QIODevice *data)
675{
676 Q_Q(QNetworkReplyImpl);
677 if (!q->isOpen())
678 return;
679
680 // read until EOF from data
681 if (Q_UNLIKELY(copyDevice)) {
682 qCritical(msg: "QNetworkReplyImpl: copy from QIODevice already in progress -- "
683 "backend probly needs to be fixed");
684 return;
685 }
686
687 copyDevice = data;
688 q->connect(asender: copyDevice, SIGNAL(readyRead()), SLOT(_q_copyReadyRead()));
689 q->connect(asender: copyDevice, SIGNAL(readChannelFinished()), SLOT(_q_copyReadChannelFinished()));
690
691 // start the copy:
692 _q_copyReadyRead();
693}
694
695void QNetworkReplyImplPrivate::appendDownstreamData(const QByteArray &data)
696{
697 Q_UNUSED(data)
698 // TODO implement
699
700 // TODO call
701
702 qFatal(msg: "QNetworkReplyImplPrivate::appendDownstreamData not implemented");
703}
704
705static void downloadBufferDeleter(char *ptr)
706{
707 delete[] ptr;
708}
709
710char* QNetworkReplyImplPrivate::getDownloadBuffer(qint64 size)
711{
712 Q_Q(QNetworkReplyImpl);
713
714 if (!downloadBuffer) {
715 // We are requested to create it
716 // Check attribute() if allocating a buffer of that size can be allowed
717 QVariant bufferAllocationPolicy = request.attribute(code: QNetworkRequest::MaximumDownloadBufferSizeAttribute);
718 if (bufferAllocationPolicy.isValid() && bufferAllocationPolicy.toLongLong() >= size) {
719 downloadBufferCurrentSize = 0;
720 downloadBufferMaximumSize = size;
721 downloadBuffer = new char[downloadBufferMaximumSize]; // throws if allocation fails
722 downloadBufferPointer = QSharedPointer<char>(downloadBuffer, downloadBufferDeleter);
723
724 q->setAttribute(code: QNetworkRequest::DownloadBufferAttribute, value: QVariant::fromValue<QSharedPointer<char> > (value: downloadBufferPointer));
725 }
726 }
727
728 return downloadBuffer;
729}
730
731void QNetworkReplyImplPrivate::setDownloadBuffer(QSharedPointer<char> sp, qint64 size)
732{
733 Q_Q(QNetworkReplyImpl);
734
735 downloadBufferPointer = sp;
736 downloadBuffer = downloadBufferPointer.data();
737 downloadBufferCurrentSize = 0;
738 downloadBufferMaximumSize = size;
739 q->setAttribute(code: QNetworkRequest::DownloadBufferAttribute, value: QVariant::fromValue<QSharedPointer<char> > (value: downloadBufferPointer));
740}
741
742
743void QNetworkReplyImplPrivate::appendDownstreamDataDownloadBuffer(qint64 bytesReceived, qint64 bytesTotal)
744{
745 Q_Q(QNetworkReplyImpl);
746 if (!q->isOpen())
747 return;
748
749 if (cacheEnabled && !cacheSaveDevice)
750 initCacheSaveDevice();
751
752 if (cacheSaveDevice && bytesReceived == bytesTotal) {
753// if (lastBytesDownloaded == -1)
754// lastBytesDownloaded = 0;
755// cacheSaveDevice->write(downloadBuffer + lastBytesDownloaded, bytesReceived - lastBytesDownloaded);
756
757 // Write everything in one go if we use a download buffer. might be more performant.
758 cacheSaveDevice->write(data: downloadBuffer, len: bytesTotal);
759 }
760
761 bytesDownloaded = bytesReceived;
762 lastBytesDownloaded = bytesReceived;
763
764 downloadBufferCurrentSize = bytesReceived;
765
766 // Only emit readyRead when actual data is there
767 // emit readyRead before downloadProgress incase this will cause events to be
768 // processed and we get into a recursive call (as in QProgressDialog).
769 if (bytesDownloaded > 0)
770 emit q->readyRead();
771 if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
772 downloadProgressSignalChoke.restart();
773 emit q->downloadProgress(bytesReceived: bytesDownloaded, bytesTotal);
774 }
775}
776
777void QNetworkReplyImplPrivate::finished()
778{
779 Q_Q(QNetworkReplyImpl);
780
781 if (state == Finished || state == Aborted || state == WaitingForSession)
782 return;
783
784 pauseNotificationHandling();
785 QVariant totalSize = cookedHeaders.value(key: QNetworkRequest::ContentLengthHeader);
786 if (preMigrationDownloaded != Q_INT64_C(-1))
787 totalSize = totalSize.toLongLong() + preMigrationDownloaded;
788
789 if (!manager.isNull()) {
790#ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
791 QSharedPointer<QNetworkSession> session (manager->d_func()->getNetworkSession());
792 if (session && session->state() == QNetworkSession::Roaming &&
793 state == Working && errorCode != QNetworkReply::OperationCanceledError) {
794 // only content with a known size will fail with a temporary network failure error
795 if (!totalSize.isNull()) {
796 if (bytesDownloaded != totalSize) {
797 if (migrateBackend()) {
798 // either we are migrating or the request is finished/aborted
799 if (state == Reconnecting || state == WaitingForSession) {
800 resumeNotificationHandling();
801 return; // exit early if we are migrating.
802 }
803 } else {
804 error(code: QNetworkReply::TemporaryNetworkFailureError,
805 errorString: QNetworkReply::tr(s: "Temporary network failure."));
806 }
807 }
808 }
809 }
810#endif
811 }
812 resumeNotificationHandling();
813
814 state = Finished;
815 q->setFinished(true);
816
817 pendingNotifications.clear();
818
819 pauseNotificationHandling();
820 if (totalSize.isNull() || totalSize == -1) {
821 emit q->downloadProgress(bytesReceived: bytesDownloaded, bytesTotal: bytesDownloaded);
822 } else {
823 emit q->downloadProgress(bytesReceived: bytesDownloaded, bytesTotal: totalSize.toLongLong());
824 }
825
826 if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
827 emit q->uploadProgress(bytesSent: 0, bytesTotal: 0);
828 resumeNotificationHandling();
829
830 // if we don't know the total size of or we received everything save the cache
831 if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize)
832 completeCacheSave();
833
834 // note: might not be a good idea, since users could decide to delete us
835 // which would delete the backend too...
836 // maybe we should protect the backend
837 pauseNotificationHandling();
838 emit q->readChannelFinished();
839 emit q->finished();
840 resumeNotificationHandling();
841}
842
843void QNetworkReplyImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
844{
845 Q_Q(QNetworkReplyImpl);
846 // Can't set and emit multiple errors.
847 if (errorCode != QNetworkReply::NoError) {
848 qWarning( msg: "QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.");
849 return;
850 }
851
852 errorCode = code;
853 q->setErrorString(errorMessage);
854
855 // note: might not be a good idea, since users could decide to delete us
856 // which would delete the backend too...
857 // maybe we should protect the backend
858 emit q->errorOccurred(code);
859}
860
861void QNetworkReplyImplPrivate::metaDataChanged()
862{
863 Q_Q(QNetworkReplyImpl);
864 // 1. do we have cookies?
865 // 2. are we allowed to set them?
866 if (!manager.isNull()) {
867 const auto it = cookedHeaders.constFind(key: QNetworkRequest::SetCookieHeader);
868 if (it != cookedHeaders.cend()
869 && request.attribute(code: QNetworkRequest::CookieSaveControlAttribute,
870 defaultValue: QNetworkRequest::Automatic).toInt() == QNetworkRequest::Automatic) {
871 QNetworkCookieJar *jar = manager->cookieJar();
872 if (jar) {
873 QList<QNetworkCookie> cookies =
874 qvariant_cast<QList<QNetworkCookie> >(v: it.value());
875 jar->setCookiesFromUrl(cookieList: cookies, url);
876 }
877 }
878 }
879
880 emit q->metaDataChanged();
881}
882
883void QNetworkReplyImplPrivate::redirectionRequested(const QUrl &target)
884{
885 attributes.insert(key: QNetworkRequest::RedirectionTargetAttribute, value: target);
886}
887
888void QNetworkReplyImplPrivate::encrypted()
889{
890#ifndef QT_NO_SSL
891 Q_Q(QNetworkReplyImpl);
892 emit q->encrypted();
893#endif
894}
895
896void QNetworkReplyImplPrivate::sslErrors(const QList<QSslError> &errors)
897{
898#ifndef QT_NO_SSL
899 Q_Q(QNetworkReplyImpl);
900 emit q->sslErrors(errors);
901#else
902 Q_UNUSED(errors);
903#endif
904}
905
906QNetworkReplyImpl::QNetworkReplyImpl(QObject *parent)
907 : QNetworkReply(*new QNetworkReplyImplPrivate, parent)
908{
909}
910
911QNetworkReplyImpl::~QNetworkReplyImpl()
912{
913 Q_D(QNetworkReplyImpl);
914
915 // This code removes the data from the cache if it was prematurely aborted.
916 // See QNetworkReplyImplPrivate::completeCacheSave(), we disable caching there after the cache
917 // save had been properly finished. So if it is still enabled it means we got deleted/aborted.
918 if (d->isCachingEnabled())
919 d->networkCache()->remove(url: url());
920}
921
922void QNetworkReplyImpl::abort()
923{
924 Q_D(QNetworkReplyImpl);
925 if (d->state == QNetworkReplyPrivate::Finished || d->state == QNetworkReplyPrivate::Aborted)
926 return;
927
928 // stop both upload and download
929 if (d->outgoingData)
930 disconnect(sender: d->outgoingData, signal: nullptr, receiver: this, member: nullptr);
931 if (d->copyDevice)
932 disconnect(sender: d->copyDevice, signal: nullptr, receiver: this, member: nullptr);
933
934 QNetworkReply::close();
935
936 // call finished which will emit signals
937 d->error(code: OperationCanceledError, errorMessage: tr(s: "Operation canceled"));
938 if (d->state == QNetworkReplyPrivate::WaitingForSession)
939 d->state = QNetworkReplyPrivate::Working;
940 d->finished();
941 d->state = QNetworkReplyPrivate::Aborted;
942
943 // finished may access the backend
944 if (d->backend) {
945 d->backend->deleteLater();
946 d->backend = nullptr;
947 }
948}
949
950void QNetworkReplyImpl::close()
951{
952 Q_D(QNetworkReplyImpl);
953 if (d->state == QNetworkReplyPrivate::Aborted ||
954 d->state == QNetworkReplyPrivate::Finished)
955 return;
956
957 // stop the download
958 if (d->backend)
959 d->backend->closeDownstreamChannel();
960 if (d->copyDevice)
961 disconnect(sender: d->copyDevice, signal: nullptr, receiver: this, member: nullptr);
962
963 QNetworkReply::close();
964
965 // call finished which will emit signals
966 d->error(code: OperationCanceledError, errorMessage: tr(s: "Operation canceled"));
967 d->finished();
968}
969
970/*!
971 Returns the number of bytes available for reading with
972 QIODevice::read(). The number of bytes available may grow until
973 the finished() signal is emitted.
974*/
975qint64 QNetworkReplyImpl::bytesAvailable() const
976{
977 // Special case for the "zero copy" download buffer
978 Q_D(const QNetworkReplyImpl);
979 if (d->downloadBuffer) {
980 qint64 maxAvail = d->downloadBufferCurrentSize - d->downloadBufferReadPosition;
981 return QNetworkReply::bytesAvailable() + maxAvail;
982 }
983
984 return QNetworkReply::bytesAvailable();
985}
986
987void QNetworkReplyImpl::setReadBufferSize(qint64 size)
988{
989 Q_D(QNetworkReplyImpl);
990 if (size > d->readBufferMaxSize &&
991 size > d->buffer.size())
992 d->backendNotify(notification: QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
993
994 QNetworkReply::setReadBufferSize(size);
995
996 if (d->backend)
997 d->backend->setDownstreamLimited(d->readBufferMaxSize > 0);
998}
999
1000#ifndef QT_NO_SSL
1001void QNetworkReplyImpl::sslConfigurationImplementation(QSslConfiguration &configuration) const
1002{
1003 Q_D(const QNetworkReplyImpl);
1004 if (d->backend)
1005 d->backend->fetchSslConfiguration(configuration);
1006}
1007
1008void QNetworkReplyImpl::setSslConfigurationImplementation(const QSslConfiguration &config)
1009{
1010 Q_D(QNetworkReplyImpl);
1011 if (d->backend && !config.isNull())
1012 d->backend->setSslConfiguration(config);
1013}
1014
1015void QNetworkReplyImpl::ignoreSslErrors()
1016{
1017 Q_D(QNetworkReplyImpl);
1018 if (d->backend)
1019 d->backend->ignoreSslErrors();
1020}
1021
1022void QNetworkReplyImpl::ignoreSslErrorsImplementation(const QList<QSslError> &errors)
1023{
1024 Q_D(QNetworkReplyImpl);
1025 if (d->backend)
1026 d->backend->ignoreSslErrors(errors);
1027}
1028#endif // QT_NO_SSL
1029
1030/*!
1031 \internal
1032*/
1033qint64 QNetworkReplyImpl::readData(char *data, qint64 maxlen)
1034{
1035 Q_D(QNetworkReplyImpl);
1036
1037 // Special case code if we have the "zero copy" download buffer
1038 if (d->downloadBuffer) {
1039 qint64 maxAvail = qMin<qint64>(a: d->downloadBufferCurrentSize - d->downloadBufferReadPosition, b: maxlen);
1040 if (maxAvail == 0)
1041 return d->state == QNetworkReplyPrivate::Finished ? -1 : 0;
1042 // FIXME what about "Aborted" state?
1043 memcpy(dest: data, src: d->downloadBuffer + d->downloadBufferReadPosition, n: maxAvail);
1044 d->downloadBufferReadPosition += maxAvail;
1045 return maxAvail;
1046 }
1047
1048
1049 // FIXME what about "Aborted" state?
1050 if (d->state == QNetworkReplyPrivate::Finished)
1051 return -1;
1052
1053 d->backendNotify(notification: QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
1054 return 0;
1055}
1056
1057/*!
1058 \internal Reimplemented for internal purposes
1059*/
1060bool QNetworkReplyImpl::event(QEvent *e)
1061{
1062 if (e->type() == QEvent::NetworkReplyUpdated) {
1063 d_func()->handleNotifications();
1064 return true;
1065 }
1066
1067 return QObject::event(event: e);
1068}
1069
1070/*
1071 Migrates the backend of the QNetworkReply to a new network connection if required. Returns
1072 true if the reply is migrated or it is not required; otherwise returns \c false.
1073*/
1074bool QNetworkReplyImplPrivate::migrateBackend()
1075{
1076 Q_Q(QNetworkReplyImpl);
1077
1078 // Network reply is already finished or aborted, don't need to migrate.
1079 if (state == Finished || state == Aborted)
1080 return true;
1081
1082 // Request has outgoing data, not migrating.
1083 if (outgoingData)
1084 return false;
1085
1086 // Request is serviced from the cache, don't need to migrate.
1087 if (copyDevice)
1088 return true;
1089
1090 // Backend does not support resuming download.
1091 if (backend && !backend->canResume())
1092 return false;
1093
1094 state = QNetworkReplyPrivate::Reconnecting;
1095
1096 cookedHeaders.clear();
1097 rawHeaders.clear();
1098
1099 preMigrationDownloaded = bytesDownloaded;
1100
1101 delete backend;
1102 backend = manager->d_func()->findBackend(op: operation, request);
1103
1104 if (backend) {
1105 backend->setParent(q);
1106 backend->reply = this;
1107 backend->setResumeOffset(bytesDownloaded);
1108 }
1109
1110 QMetaObject::invokeMethod(obj: q, member: "_q_startOperation", type: Qt::QueuedConnection);
1111
1112 return true;
1113}
1114
1115QT_END_NAMESPACE
1116
1117#include "moc_qnetworkreplyimpl_p.cpp"
1118
1119

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