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 | 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 | |
135 | void QNetworkReplyImplPrivate::_q_copyReadChannelFinished() |
136 | { |
137 | _q_copyReadyRead(); |
138 | } |
139 | |
140 | void 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 | |
157 | void 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 | |
200 | void 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 | |
272 | void 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 | |
283 | void 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 |
308 | void QNetworkReplyImplPrivate::pauseNotificationHandling() |
309 | { |
310 | notificationHandlingPaused = true; |
311 | } |
312 | |
313 | // Resume notification handling |
314 | void 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 | |
322 | QAbstractNetworkCache *QNetworkReplyImplPrivate::networkCache() const |
323 | { |
324 | if (!backend) |
325 | return nullptr; |
326 | return backend->networkCache(); |
327 | } |
328 | |
329 | void 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 | |
338 | bool QNetworkReplyImplPrivate::isCachingEnabled() const |
339 | { |
340 | return (cacheEnabled && networkCache() != nullptr); |
341 | } |
342 | |
343 | void 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 | |
370 | void 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 | |
381 | void 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 | |
404 | qint64 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 | |
413 | void 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) |
454 | void 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 | |
481 | void 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? |
505 | void 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 | |
526 | char* 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 | |
547 | void 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 | |
559 | void 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 | |
588 | void 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 | |
631 | void 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 | |
649 | void 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 | |
671 | void QNetworkReplyImplPrivate::redirectionRequested(const QUrl &target) |
672 | { |
673 | attributes.insert(key: QNetworkRequest::RedirectionTargetAttribute, value: target); |
674 | } |
675 | |
676 | void QNetworkReplyImplPrivate::encrypted() |
677 | { |
678 | #ifndef QT_NO_SSL |
679 | Q_Q(QNetworkReplyImpl); |
680 | emit q->encrypted(); |
681 | #endif |
682 | } |
683 | |
684 | void 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 | |
694 | void 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 | |
721 | QNetworkReplyImpl::QNetworkReplyImpl(QObject *parent) |
722 | : QNetworkReply(*new QNetworkReplyImplPrivate, parent) |
723 | { |
724 | } |
725 | |
726 | QNetworkReplyImpl::~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 | |
737 | void 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 | |
763 | void 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 | */ |
788 | qint64 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 | |
799 | void 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 |
809 | void QNetworkReplyImpl::sslConfigurationImplementation(QSslConfiguration &configuration) const |
810 | { |
811 | Q_D(const QNetworkReplyImpl); |
812 | if (d->backend) |
813 | configuration = d->backend->sslConfiguration(); |
814 | } |
815 | |
816 | void QNetworkReplyImpl::setSslConfigurationImplementation(const QSslConfiguration &config) |
817 | { |
818 | Q_D(QNetworkReplyImpl); |
819 | if (d->backend && !config.isNull()) |
820 | d->backend->setSslConfiguration(config); |
821 | } |
822 | |
823 | void QNetworkReplyImpl::ignoreSslErrors() |
824 | { |
825 | Q_D(QNetworkReplyImpl); |
826 | if (d->backend) |
827 | d->backend->ignoreSslErrors(); |
828 | } |
829 | |
830 | void 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 | */ |
841 | qint64 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 | */ |
898 | bool 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 | |
908 | QT_END_NAMESPACE |
909 | |
910 | #include "moc_qnetworkreplyimpl_p.cpp" |
911 | |
912 |
Definitions
- QNetworkReplyImplPrivate
- _q_startOperation
- _q_copyReadyRead
- _q_copyReadChannelFinished
- _q_bufferOutgoingDataFinished
- _q_bufferOutgoingData
- setup
- backendNotify
- handleNotifications
- pauseNotificationHandling
- resumeNotificationHandling
- networkCache
- createCache
- isCachingEnabled
- setCachingEnabled
- completeCacheSave
- emitUploadProgress
- nextDownstreamBlockSize
- initCacheSaveDevice
- appendDownstreamData
- appendDownstreamDataSignalEmissions
- appendDownstreamData
- getDownloadBuffer
- setDownloadBuffer
- appendDownstreamDataDownloadBuffer
- finished
- error
- metaDataChanged
- redirectionRequested
- encrypted
- sslErrors
- readFromBackend
- QNetworkReplyImpl
- ~QNetworkReplyImpl
- abort
- close
- bytesAvailable
- setReadBufferSize
- sslConfigurationImplementation
- setSslConfigurationImplementation
- ignoreSslErrors
- ignoreSslErrorsImplementation
- readData
Learn to use CMake with our Intro Training
Find out more