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

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