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 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | QT_IMPL_METATYPE_EXTERN_TAGGED(QSharedPointer<char>, QSharedPointer_char) |
19 | |
20 | inline 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 | |
37 | void 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 | |
82 | void 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 | |
134 | void QNetworkReplyImplPrivate::_q_copyReadChannelFinished() |
135 | { |
136 | _q_copyReadyRead(); |
137 | } |
138 | |
139 | void 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 | |
156 | void 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 | |
199 | void 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 | |
268 | void 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 | |
279 | void 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 |
304 | void QNetworkReplyImplPrivate::pauseNotificationHandling() |
305 | { |
306 | notificationHandlingPaused = true; |
307 | } |
308 | |
309 | // Resume notification handling |
310 | void 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 | |
318 | QAbstractNetworkCache *QNetworkReplyImplPrivate::networkCache() const |
319 | { |
320 | if (!backend) |
321 | return nullptr; |
322 | return backend->networkCache(); |
323 | } |
324 | |
325 | void 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 | |
334 | bool QNetworkReplyImplPrivate::isCachingEnabled() const |
335 | { |
336 | return (cacheEnabled && networkCache() != nullptr); |
337 | } |
338 | |
339 | void 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 | |
366 | void 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 | |
377 | void 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 | |
400 | qint64 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 | |
409 | void 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) |
450 | void 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 | |
477 | void 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? |
501 | void 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 | |
522 | char* 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 | |
543 | void 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 | |
555 | void 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 | |
584 | void 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 | |
625 | void 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 | |
643 | void 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 | |
665 | void QNetworkReplyImplPrivate::redirectionRequested(const QUrl &target) |
666 | { |
667 | attributes.insert(key: QNetworkRequest::RedirectionTargetAttribute, value: target); |
668 | } |
669 | |
670 | void QNetworkReplyImplPrivate::encrypted() |
671 | { |
672 | #ifndef QT_NO_SSL |
673 | Q_Q(QNetworkReplyImpl); |
674 | emit q->encrypted(); |
675 | #endif |
676 | } |
677 | |
678 | void 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 | |
688 | void 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 | |
715 | QNetworkReplyImpl::QNetworkReplyImpl(QObject *parent) |
716 | : QNetworkReply(*new QNetworkReplyImplPrivate, parent) |
717 | { |
718 | } |
719 | |
720 | QNetworkReplyImpl::~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 | |
731 | void 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 | |
757 | void 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 | */ |
782 | qint64 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 | |
793 | void 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 |
803 | void QNetworkReplyImpl::sslConfigurationImplementation(QSslConfiguration &configuration) const |
804 | { |
805 | Q_D(const QNetworkReplyImpl); |
806 | if (d->backend) |
807 | configuration = d->backend->sslConfiguration(); |
808 | } |
809 | |
810 | void QNetworkReplyImpl::setSslConfigurationImplementation(const QSslConfiguration &config) |
811 | { |
812 | Q_D(QNetworkReplyImpl); |
813 | if (d->backend && !config.isNull()) |
814 | d->backend->setSslConfiguration(config); |
815 | } |
816 | |
817 | void QNetworkReplyImpl::ignoreSslErrors() |
818 | { |
819 | Q_D(QNetworkReplyImpl); |
820 | if (d->backend) |
821 | d->backend->ignoreSslErrors(); |
822 | } |
823 | |
824 | void 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 | */ |
835 | qint64 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 | */ |
891 | bool 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 | |
901 | QT_END_NAMESPACE |
902 | |
903 | #include "moc_qnetworkreplyimpl_p.cpp" |
904 | |
905 | |