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

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