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