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