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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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