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
53 QT_BEGIN_NAMESPACE
54
QNetworkReplyImplPrivate()55 inline 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(QNetworkRequest::EmitAllUploadProgressSignalsAttribute).toBool() == true)
69 emitAllUploadProgressSignals = true;
70 }
71
_q_startOperation()72 void 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(QNetworkReplyImpl::ProtocolUnknownError,
86 QCoreApplication::translate("QNetworkReply", "Protocol \"%1\" is unknown").arg(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(QNetworkRequest::BackgroundRequestAttribute, QVariant::fromValue(false));
96 if (isBackground.toBool() && session && session->usagePolicies().testFlag(QNetworkSession::NoBackgroundTrafficPolicy)) {
97 error(QNetworkReply::BackgroundRequestNotAllowedError,
98 QCoreApplication::translate("QNetworkReply", "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(session.data(), SIGNAL(error(QNetworkSession::SessionError)),
113 q, SLOT(_q_networkSessionFailed()));
114
115 if (!session->isOpen()) {
116 session->setSessionProperty(QStringLiteral("ConnectInBackground"), isBackground);
117 session->open();
118 }
119 } else {
120 qWarning("Backend is waiting for QNetworkSession to connect, but there is none!");
121 state = Working;
122 error(QNetworkReplyImpl::NetworkSessionFailedError,
123 QCoreApplication::translate("QNetworkReply", "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(session.data(), SIGNAL(stateChanged(QNetworkSession::State)),
138 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(session.data(), SIGNAL(usagePoliciesChanged(QNetworkSession::UsagePolicies)),
147 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(NotifyDownstreamReadyWrite);
162
163 handleNotifications();
164 }
165 }
166 }
167
_q_copyReadyRead()168 void 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>(1, bytesToRead, copyDevice->bytesAvailable());
187 qint64 bytesActuallyRead = copyDevice->read(buffer.reserve(bytesToRead), bytesToRead);
188 if (bytesActuallyRead == -1) {
189 buffer.chop(bytesToRead);
190 backendNotify(NotifyCopyFinished);
191 break;
192 }
193 buffer.chop(bytesToRead - bytesActuallyRead);
194
195 if (!copyDevice->isSequential() && copyDevice->atEnd()) {
196 backendNotify(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(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(bytesDownloaded,
220 totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
221 }
222 resumeNotificationHandling();
223 }
224
_q_copyReadChannelFinished()225 void QNetworkReplyImplPrivate::_q_copyReadChannelFinished()
226 {
227 _q_copyReadyRead();
228 }
229
_q_bufferOutgoingDataFinished()230 void 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(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
241 QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
242
243 // finally, start the request
244 QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
245 }
246
_q_bufferOutgoingData()247 void 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(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
256 QObject::connect(outgoingData, SIGNAL(readChannelFinished()), 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(bytesToBuffer);
270 bytesBuffered = outgoingData->read(dst, bytesToBuffer);
271
272 if (bytesBuffered == -1) {
273 // EOF has been reached.
274 outgoingDataBuffer->chop(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(bytesToBuffer);
281
282 break;
283 } else {
284 // don't break, try to read() again
285 outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered);
286 }
287 }
288 }
289
290 #ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
_q_networkSessionConnected()291 void 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(q, "_q_startOperation", Qt::QueuedConnection);
315 break;
316 default:
317 ;
318 }
319 }
320
_q_networkSessionStateChanged(QNetworkSession::State sessionState)321 void QNetworkReplyImplPrivate::_q_networkSessionStateChanged(QNetworkSession::State sessionState)
322 {
323 if (sessionState == QNetworkSession::Disconnected
324 && state != Idle && state != Reconnecting) {
325 error(QNetworkReplyImpl::NetworkSessionFailedError,
326 QCoreApplication::translate("QNetworkReply", "Network session error."));
327 finished();
328 }
329 }
330
_q_networkSessionFailed()331 void 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("QNetworkReply", "Network session error.");
342 error(QNetworkReplyImpl::NetworkSessionFailedError, errorStr);
343 finished();
344 }
345 }
346
_q_networkSessionUsagePoliciesChanged(QNetworkSession::UsagePolicies newPolicies)347 void QNetworkReplyImplPrivate::_q_networkSessionUsagePoliciesChanged(QNetworkSession::UsagePolicies newPolicies)
348 {
349 if (backend->request().attribute(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(QNetworkReply::BackgroundRequestNotAllowedError,
355 QCoreApplication::translate("QNetworkReply", "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
setup(QNetworkAccessManager::Operation op,const QNetworkRequest & req,QIODevice * data)364 void 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(QIODevice::ReadOnly);
376 // Internal code that does a HTTP reply for the synchronous Ajax
377 // in Qt WebKit.
378 QVariant synchronousHttpAttribute = req.attribute(
379 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(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(q, "_q_startOperation", Qt::QueuedConnection);
403 } else {
404 bool bufferingDisallowed =
405 req.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute,
406 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(QNetworkRequest::ContentLengthHeader).isValid()) {
412 QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
413 } else {
414 state = Buffering;
415 QMetaObject::invokeMethod(q, "_q_bufferOutgoingData", Qt::QueuedConnection);
416 }
417 } else {
418 // _q_startOperation will be called when the buffering has finished.
419 state = Buffering;
420 QMetaObject::invokeMethod(q, "_q_bufferOutgoingData", 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(q, "_q_startOperation", Qt::QueuedConnection);
430 }
431 }
432
backendNotify(InternalNotifications notification)433 void QNetworkReplyImplPrivate::backendNotify(InternalNotifications notification)
434 {
435 Q_Q(QNetworkReplyImpl);
436 const auto it = std::find(pendingNotifications.cbegin(), pendingNotifications.cend(), notification);
437 if (it == pendingNotifications.cend())
438 pendingNotifications.push_back(notification);
439
440 if (pendingNotifications.size() == 1)
441 QCoreApplication::postEvent(q, new QEvent(QEvent::NetworkReplyUpdated));
442 }
443
handleNotifications()444 void QNetworkReplyImplPrivate::handleNotifications()
445 {
446 if (notificationHandlingPaused)
447 return;
448
449 for (InternalNotifications notification : qExchange(pendingNotifications, {})) {
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(copyDevice, 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
pauseNotificationHandling()475 void QNetworkReplyImplPrivate::pauseNotificationHandling()
476 {
477 notificationHandlingPaused = true;
478 }
479
480 // Resume notification handling
resumeNotificationHandling()481 void QNetworkReplyImplPrivate::resumeNotificationHandling()
482 {
483 Q_Q(QNetworkReplyImpl);
484 notificationHandlingPaused = false;
485 if (pendingNotifications.size() >= 1)
486 QCoreApplication::postEvent(q, new QEvent(QEvent::NetworkReplyUpdated));
487 }
488
networkCache() const489 QAbstractNetworkCache *QNetworkReplyImplPrivate::networkCache() const
490 {
491 if (!backend)
492 return nullptr;
493 return backend->networkCache();
494 }
495
createCache()496 void QNetworkReplyImplPrivate::createCache()
497 {
498 // check if we can save and if we're allowed to
499 if (!networkCache()
500 || !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool())
501 return;
502 cacheEnabled = true;
503 }
504
isCachingEnabled() const505 bool QNetworkReplyImplPrivate::isCachingEnabled() const
506 {
507 return (cacheEnabled && networkCache() != nullptr);
508 }
509
setCachingEnabled(bool enable)510 void 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("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("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
completeCacheSave()537 void QNetworkReplyImplPrivate::completeCacheSave()
538 {
539 if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) {
540 networkCache()->remove(url);
541 } else if (cacheEnabled && cacheSaveDevice) {
542 networkCache()->insert(cacheSaveDevice);
543 }
544 cacheSaveDevice = nullptr;
545 cacheEnabled = false;
546 }
547
emitUploadProgress(qint64 bytesSent,qint64 bytesTotal)548 void 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
nextDownstreamBlockSize() const571 qint64 QNetworkReplyImplPrivate::nextDownstreamBlockSize() const
572 {
573 enum { DesiredBufferSize = 32 * 1024 };
574 if (readBufferMaxSize == 0)
575 return DesiredBufferSize;
576
577 return qMax<qint64>(0, readBufferMaxSize - buffer.size());
578 }
579
initCacheSaveDevice()580 void 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(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(QNetworkRequest::RedirectionTargetAttribute);
598 if (redirectionTarget.isValid()) {
599 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
600 attributes.insert(QNetworkRequest::RedirectionTargetAttribute, 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("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)
appendDownstreamData(QByteDataBuffer & data)620 void 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(item.constData(), item.size());
636 buffer.append(item);
637
638 bytesWritten += item.size();
639 }
640 data.clear();
641
642 bytesDownloaded += bytesWritten;
643 lastBytesDownloaded = bytesDownloaded;
644
645 appendDownstreamDataSignalEmissions();
646 }
647
appendDownstreamDataSignalEmissions()648 void QNetworkReplyImplPrivate::appendDownstreamDataSignalEmissions()
649 {
650 Q_Q(QNetworkReplyImpl);
651
652 QVariant totalSize = cookedHeaders.value(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(bytesDownloaded,
664 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(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
671 }
672
673 // this is used when it was fetched from the cache, right?
appendDownstreamData(QIODevice * data)674 void 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("QNetworkReplyImpl: copy from QIODevice already in progress -- "
683 "backend probly needs to be fixed");
684 return;
685 }
686
687 copyDevice = data;
688 q->connect(copyDevice, SIGNAL(readyRead()), SLOT(_q_copyReadyRead()));
689 q->connect(copyDevice, SIGNAL(readChannelFinished()), SLOT(_q_copyReadChannelFinished()));
690
691 // start the copy:
692 _q_copyReadyRead();
693 }
694
appendDownstreamData(const QByteArray & data)695 void QNetworkReplyImplPrivate::appendDownstreamData(const QByteArray &data)
696 {
697 Q_UNUSED(data)
698 // TODO implement
699
700 // TODO call
701
702 qFatal("QNetworkReplyImplPrivate::appendDownstreamData not implemented");
703 }
704
downloadBufferDeleter(char * ptr)705 static void downloadBufferDeleter(char *ptr)
706 {
707 delete[] ptr;
708 }
709
getDownloadBuffer(qint64 size)710 char* 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(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(QNetworkRequest::DownloadBufferAttribute, QVariant::fromValue<QSharedPointer<char> > (downloadBufferPointer));
725 }
726 }
727
728 return downloadBuffer;
729 }
730
setDownloadBuffer(QSharedPointer<char> sp,qint64 size)731 void 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(QNetworkRequest::DownloadBufferAttribute, QVariant::fromValue<QSharedPointer<char> > (downloadBufferPointer));
740 }
741
742
appendDownstreamDataDownloadBuffer(qint64 bytesReceived,qint64 bytesTotal)743 void 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(downloadBuffer, 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(bytesDownloaded, bytesTotal);
774 }
775 }
776
finished()777 void 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(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(QNetworkReply::TemporaryNetworkFailureError,
805 QNetworkReply::tr("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(bytesDownloaded, bytesDownloaded);
822 } else {
823 emit q->downloadProgress(bytesDownloaded, totalSize.toLongLong());
824 }
825
826 if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
827 emit q->uploadProgress(0, 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
error(QNetworkReplyImpl::NetworkError code,const QString & errorMessage)843 void 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( "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
metaDataChanged()861 void 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(QNetworkRequest::SetCookieHeader);
868 if (it != cookedHeaders.cend()
869 && request.attribute(QNetworkRequest::CookieSaveControlAttribute,
870 QNetworkRequest::Automatic).toInt() == QNetworkRequest::Automatic) {
871 QNetworkCookieJar *jar = manager->cookieJar();
872 if (jar) {
873 QList<QNetworkCookie> cookies =
874 qvariant_cast<QList<QNetworkCookie> >(it.value());
875 jar->setCookiesFromUrl(cookies, url);
876 }
877 }
878 }
879
880 emit q->metaDataChanged();
881 }
882
redirectionRequested(const QUrl & target)883 void QNetworkReplyImplPrivate::redirectionRequested(const QUrl &target)
884 {
885 attributes.insert(QNetworkRequest::RedirectionTargetAttribute, target);
886 }
887
encrypted()888 void QNetworkReplyImplPrivate::encrypted()
889 {
890 #ifndef QT_NO_SSL
891 Q_Q(QNetworkReplyImpl);
892 emit q->encrypted();
893 #endif
894 }
895
sslErrors(const QList<QSslError> & errors)896 void 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
QNetworkReplyImpl(QObject * parent)906 QNetworkReplyImpl::QNetworkReplyImpl(QObject *parent)
907 : QNetworkReply(*new QNetworkReplyImplPrivate, parent)
908 {
909 }
910
~QNetworkReplyImpl()911 QNetworkReplyImpl::~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());
920 }
921
abort()922 void 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(d->outgoingData, nullptr, this, nullptr);
931 if (d->copyDevice)
932 disconnect(d->copyDevice, nullptr, this, nullptr);
933
934 QNetworkReply::close();
935
936 // call finished which will emit signals
937 d->error(OperationCanceledError, tr("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
close()950 void 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(d->copyDevice, nullptr, this, nullptr);
962
963 QNetworkReply::close();
964
965 // call finished which will emit signals
966 d->error(OperationCanceledError, tr("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 */
bytesAvailable() const975 qint64 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
setReadBufferSize(qint64 size)987 void QNetworkReplyImpl::setReadBufferSize(qint64 size)
988 {
989 Q_D(QNetworkReplyImpl);
990 if (size > d->readBufferMaxSize &&
991 size > d->buffer.size())
992 d->backendNotify(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
sslConfigurationImplementation(QSslConfiguration & configuration) const1001 void QNetworkReplyImpl::sslConfigurationImplementation(QSslConfiguration &configuration) const
1002 {
1003 Q_D(const QNetworkReplyImpl);
1004 if (d->backend)
1005 d->backend->fetchSslConfiguration(configuration);
1006 }
1007
setSslConfigurationImplementation(const QSslConfiguration & config)1008 void QNetworkReplyImpl::setSslConfigurationImplementation(const QSslConfiguration &config)
1009 {
1010 Q_D(QNetworkReplyImpl);
1011 if (d->backend && !config.isNull())
1012 d->backend->setSslConfiguration(config);
1013 }
1014
ignoreSslErrors()1015 void QNetworkReplyImpl::ignoreSslErrors()
1016 {
1017 Q_D(QNetworkReplyImpl);
1018 if (d->backend)
1019 d->backend->ignoreSslErrors();
1020 }
1021
ignoreSslErrorsImplementation(const QList<QSslError> & errors)1022 void 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 */
readData(char * data,qint64 maxlen)1033 qint64 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>(d->downloadBufferCurrentSize - d->downloadBufferReadPosition, maxlen);
1040 if (maxAvail == 0)
1041 return d->state == QNetworkReplyPrivate::Finished ? -1 : 0;
1042 // FIXME what about "Aborted" state?
1043 memcpy(data, d->downloadBuffer + d->downloadBufferReadPosition, 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(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
1054 return 0;
1055 }
1056
1057 /*!
1058 \internal Reimplemented for internal purposes
1059 */
event(QEvent * e)1060 bool QNetworkReplyImpl::event(QEvent *e)
1061 {
1062 if (e->type() == QEvent::NetworkReplyUpdated) {
1063 d_func()->handleNotifications();
1064 return true;
1065 }
1066
1067 return QObject::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 */
migrateBackend()1074 bool 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(operation, request);
1103
1104 if (backend) {
1105 backend->setParent(q);
1106 backend->reply = this;
1107 backend->setResumeOffset(bytesDownloaded);
1108 }
1109
1110 QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
1111
1112 return true;
1113 }
1114
1115 QT_END_NAMESPACE
1116
1117 #include "moc_qnetworkreplyimpl_p.cpp"
1118
1119