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//#define QNETWORKACCESSHTTPBACKEND_DEBUG
41
42#include "qnetworkreplyhttpimpl_p.h"
43#include "qnetworkaccessmanager_p.h"
44#include "qnetworkaccesscache_p.h"
45#include "qabstractnetworkcache.h"
46#include "qnetworkrequest.h"
47#include "qnetworkreply.h"
48#include "qnetworkrequest_p.h"
49#include "qnetworkcookie.h"
50#include "qnetworkcookie_p.h"
51#include "QtCore/qdatetime.h"
52#include "QtCore/qelapsedtimer.h"
53#include "QtNetwork/qsslconfiguration.h"
54#include "qhttpthreaddelegate_p.h"
55#include "qhsts_p.h"
56#include "qthread.h"
57#include "QtCore/qcoreapplication.h"
58
59#include <QtCore/private/qthread_p.h>
60
61#include "qnetworkcookiejar.h"
62#include "qnetconmonitor_p.h"
63
64#include "qnetworkreplyimpl_p.h"
65
66#include <string.h> // for strchr
67
68QT_BEGIN_NAMESPACE
69
70class QNetworkProxy;
71
72static inline bool isSeparator(char c)
73{
74 static const char separators[] = "()<>@,;:\\\"/[]?={}";
75 return isLWS(c) || strchr(separators, c) != nullptr;
76}
77
78// ### merge with nextField in cookiejar.cpp
79static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &header)
80{
81 // The HTTP header is of the form:
82 // header = #1(directives)
83 // directives = token | value-directive
84 // value-directive = token "=" (token | quoted-string)
85 QHash<QByteArray, QByteArray> result;
86
87 int pos = 0;
88 while (true) {
89 // skip spaces
90 pos = nextNonWhitespace(header, pos);
91 if (pos == header.length())
92 return result; // end of parsing
93
94 // pos points to a non-whitespace
95 int comma = header.indexOf(',', pos);
96 int equal = header.indexOf('=', pos);
97 if (comma == pos || equal == pos)
98 // huh? Broken header.
99 return result;
100
101 // The key name is delimited by either a comma, an equal sign or the end
102 // of the header, whichever comes first
103 int end = comma;
104 if (end == -1)
105 end = header.length();
106 if (equal != -1 && end > equal)
107 end = equal; // equal sign comes before comma/end
108 QByteArray key = QByteArray(header.constData() + pos, end - pos).trimmed().toLower();
109 pos = end + 1;
110
111 if (uint(equal) < uint(comma)) {
112 // case: token "=" (token | quoted-string)
113 // skip spaces
114 pos = nextNonWhitespace(header, pos);
115 if (pos == header.length())
116 // huh? Broken header
117 return result;
118
119 QByteArray value;
120 value.reserve(header.length() - pos);
121 if (header.at(pos) == '"') {
122 // case: quoted-string
123 // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
124 // qdtext = <any TEXT except <">>
125 // quoted-pair = "\" CHAR
126 ++pos;
127 while (pos < header.length()) {
128 char c = header.at(pos);
129 if (c == '"') {
130 // end of quoted text
131 break;
132 } else if (c == '\\') {
133 ++pos;
134 if (pos >= header.length())
135 // broken header
136 return result;
137 c = header.at(pos);
138 }
139
140 value += c;
141 ++pos;
142 }
143 } else {
144 // case: token
145 while (pos < header.length()) {
146 char c = header.at(pos);
147 if (isSeparator(c))
148 break;
149 value += c;
150 ++pos;
151 }
152 }
153
154 result.insert(key, value);
155
156 // find the comma now:
157 comma = header.indexOf(',', pos);
158 if (comma == -1)
159 return result; // end of parsing
160 pos = comma + 1;
161 } else {
162 // case: token
163 // key is already set
164 result.insert(key, QByteArray());
165 }
166 }
167}
168
169QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manager,
170 const QNetworkRequest& request,
171 QNetworkAccessManager::Operation& operation,
172 QIODevice* outgoingData)
173 : QNetworkReply(*new QNetworkReplyHttpImplPrivate, manager)
174{
175 Q_D(QNetworkReplyHttpImpl);
176 Q_ASSERT(manager);
177 d->manager = manager;
178 d->managerPrivate = manager->d_func();
179 d->request = request;
180 d->originalRequest = request;
181 d->operation = operation;
182 d->outgoingData = outgoingData;
183 d->url = request.url();
184#ifndef QT_NO_SSL
185 if (request.url().scheme() == QLatin1String("https"))
186 d->sslConfiguration.reset(new QSslConfiguration(request.sslConfiguration()));
187#endif
188
189 // FIXME Later maybe set to Unbuffered, especially if it is zerocopy or from cache?
190 QIODevice::open(QIODevice::ReadOnly);
191
192
193 // Internal code that does a HTTP reply for the synchronous Ajax
194 // in Qt WebKit.
195 QVariant synchronousHttpAttribute = request.attribute(
196 static_cast<QNetworkRequest::Attribute>(QNetworkRequest::SynchronousRequestAttribute));
197 if (synchronousHttpAttribute.isValid()) {
198 d->synchronous = synchronousHttpAttribute.toBool();
199 if (d->synchronous && outgoingData) {
200 // The synchronous HTTP is a corner case, we will put all upload data in one big QByteArray in the outgoingDataBuffer.
201 // Yes, this is not the most efficient thing to do, but on the other hand synchronous XHR needs to die anyway.
202 d->outgoingDataBuffer = QSharedPointer<QRingBuffer>::create();
203 qint64 previousDataSize = 0;
204 do {
205 previousDataSize = d->outgoingDataBuffer->size();
206 d->outgoingDataBuffer->append(d->outgoingData->readAll());
207 } while (d->outgoingDataBuffer->size() != previousDataSize);
208 d->_q_startOperation();
209 return;
210 }
211 }
212
213
214 if (outgoingData) {
215 // there is data to be uploaded, e.g. HTTP POST.
216
217 if (!d->outgoingData->isSequential()) {
218 // fixed size non-sequential (random-access)
219 // just start the operation
220 QMetaObject::invokeMethod(this, "_q_startOperation", Qt::QueuedConnection);
221 // FIXME make direct call?
222 } else {
223 bool bufferingDisallowed =
224 request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute,
225 false).toBool();
226
227 if (bufferingDisallowed) {
228 // if a valid content-length header for the request was supplied, we can disable buffering
229 // if not, we will buffer anyway
230 if (request.header(QNetworkRequest::ContentLengthHeader).isValid()) {
231 QMetaObject::invokeMethod(this, "_q_startOperation", Qt::QueuedConnection);
232 // FIXME make direct call?
233 } else {
234 d->state = d->Buffering;
235 QMetaObject::invokeMethod(this, "_q_bufferOutgoingData", Qt::QueuedConnection);
236 }
237 } else {
238 // _q_startOperation will be called when the buffering has finished.
239 d->state = d->Buffering;
240 QMetaObject::invokeMethod(this, "_q_bufferOutgoingData", Qt::QueuedConnection);
241 }
242 }
243 } else {
244 // No outgoing data (POST, ..)
245 d->_q_startOperation();
246 }
247}
248
249QNetworkReplyHttpImpl::~QNetworkReplyHttpImpl()
250{
251 // This will do nothing if the request was already finished or aborted
252 emit abortHttpRequest();
253}
254
255void QNetworkReplyHttpImpl::close()
256{
257 Q_D(QNetworkReplyHttpImpl);
258
259 if (d->state == QNetworkReplyPrivate::Aborted ||
260 d->state == QNetworkReplyPrivate::Finished)
261 return;
262
263 // According to the documentation close only stops the download
264 // by closing we can ignore the download part and continue uploading.
265 QNetworkReply::close();
266
267 // call finished which will emit signals
268 // FIXME shouldn't this be emitted Queued?
269 d->error(OperationCanceledError, tr("Operation canceled"));
270 d->finished();
271}
272
273void QNetworkReplyHttpImpl::abort()
274{
275 Q_D(QNetworkReplyHttpImpl);
276 // FIXME
277 if (d->state == QNetworkReplyPrivate::Finished || d->state == QNetworkReplyPrivate::Aborted)
278 return;
279
280 QNetworkReply::close();
281
282 if (d->state != QNetworkReplyPrivate::Finished) {
283 // call finished which will emit signals
284 // FIXME shouldn't this be emitted Queued?
285 d->error(OperationCanceledError, tr("Operation canceled"));
286 d->finished();
287 }
288
289 d->state = QNetworkReplyPrivate::Aborted;
290
291 emit abortHttpRequest();
292}
293
294qint64 QNetworkReplyHttpImpl::bytesAvailable() const
295{
296 Q_D(const QNetworkReplyHttpImpl);
297
298 // if we load from cache device
299 if (d->cacheLoadDevice) {
300 return QNetworkReply::bytesAvailable() + d->cacheLoadDevice->bytesAvailable();
301 }
302
303 // zerocopy buffer
304 if (d->downloadZerocopyBuffer) {
305 return QNetworkReply::bytesAvailable() + d->downloadBufferCurrentSize - d->downloadBufferReadPosition;
306 }
307
308 // normal buffer
309 return QNetworkReply::bytesAvailable();
310}
311
312bool QNetworkReplyHttpImpl::isSequential () const
313{
314 // FIXME In the cache of a cached load or the zero-copy buffer we could actually be non-sequential.
315 // FIXME however this requires us to implement stuff like seek() too.
316 return true;
317}
318
319qint64 QNetworkReplyHttpImpl::size() const
320{
321 // FIXME At some point, this could return a proper value, e.g. if we're non-sequential.
322 return QNetworkReply::size();
323}
324
325qint64 QNetworkReplyHttpImpl::readData(char* data, qint64 maxlen)
326{
327 Q_D(QNetworkReplyHttpImpl);
328
329 // cacheload device
330 if (d->cacheLoadDevice) {
331 // FIXME bytesdownloaded, position etc?
332
333 qint64 ret = d->cacheLoadDevice->read(data, maxlen);
334 return ret;
335 }
336
337 // zerocopy buffer
338 if (d->downloadZerocopyBuffer) {
339 // FIXME bytesdownloaded, position etc?
340
341 qint64 howMuch = qMin(maxlen, (d->downloadBufferCurrentSize - d->downloadBufferReadPosition));
342 memcpy(data, d->downloadZerocopyBuffer + d->downloadBufferReadPosition, howMuch);
343 d->downloadBufferReadPosition += howMuch;
344 return howMuch;
345
346 }
347
348 // normal buffer
349 if (d->state == d->Finished || d->state == d->Aborted)
350 return -1;
351
352 qint64 wasBuffered = d->bytesBuffered;
353 d->bytesBuffered = 0;
354 if (readBufferSize())
355 emit readBufferFreed(wasBuffered);
356 return 0;
357}
358
359void QNetworkReplyHttpImpl::setReadBufferSize(qint64 size)
360{
361 QNetworkReply::setReadBufferSize(size);
362 emit readBufferSizeChanged(size);
363 return;
364}
365
366bool QNetworkReplyHttpImpl::canReadLine () const
367{
368 Q_D(const QNetworkReplyHttpImpl);
369
370 if (QNetworkReply::canReadLine())
371 return true;
372
373 if (d->cacheLoadDevice)
374 return d->cacheLoadDevice->canReadLine();
375
376 if (d->downloadZerocopyBuffer)
377 return memchr(d->downloadZerocopyBuffer + d->downloadBufferReadPosition, '\n', d->downloadBufferCurrentSize - d->downloadBufferReadPosition);
378
379 return false;
380}
381
382#ifndef QT_NO_SSL
383void QNetworkReplyHttpImpl::ignoreSslErrors()
384{
385 Q_D(QNetworkReplyHttpImpl);
386 Q_ASSERT(d->managerPrivate);
387
388 if (d->managerPrivate->stsEnabled && d->managerPrivate->stsCache.isKnownHost(url())) {
389 // We cannot ignore any Security Transport-related errors for this host.
390 return;
391 }
392
393 d->pendingIgnoreAllSslErrors = true;
394}
395
396void QNetworkReplyHttpImpl::ignoreSslErrorsImplementation(const QList<QSslError> &errors)
397{
398 Q_D(QNetworkReplyHttpImpl);
399 Q_ASSERT(d->managerPrivate);
400
401 if (d->managerPrivate->stsEnabled && d->managerPrivate->stsCache.isKnownHost(url())) {
402 // We cannot ignore any Security Transport-related errors for this host.
403 return;
404 }
405
406 // the pending list is set if QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
407 // is called before QNetworkAccessManager::get() (or post(), etc.)
408 d->pendingIgnoreSslErrorsList = errors;
409}
410
411void QNetworkReplyHttpImpl::setSslConfigurationImplementation(const QSslConfiguration &newconfig)
412{
413 // Setting a SSL configuration on a reply is not supported. The user needs to set
414 // her/his QSslConfiguration on the QNetworkRequest.
415 Q_UNUSED(newconfig);
416}
417
418void QNetworkReplyHttpImpl::sslConfigurationImplementation(QSslConfiguration &configuration) const
419{
420 Q_D(const QNetworkReplyHttpImpl);
421 if (d->sslConfiguration.data())
422 configuration = *d->sslConfiguration;
423 else
424 configuration = request().sslConfiguration();
425}
426#endif
427
428QNetworkReplyHttpImplPrivate::QNetworkReplyHttpImplPrivate()
429 : QNetworkReplyPrivate()
430 , manager(nullptr)
431 , managerPrivate(nullptr)
432 , synchronous(false)
433 , state(Idle)
434 , statusCode(0)
435 , uploadByteDevicePosition(false)
436 , uploadDeviceChoking(false)
437 , outgoingData(nullptr)
438 , bytesUploaded(-1)
439 , cacheLoadDevice(nullptr)
440 , loadingFromCache(false)
441 , cacheSaveDevice(nullptr)
442 , cacheEnabled(false)
443 , resumeOffset(0)
444 , bytesDownloaded(0)
445 , bytesBuffered(0)
446 , transferTimeout(nullptr)
447 , downloadBufferReadPosition(0)
448 , downloadBufferCurrentSize(0)
449 , downloadZerocopyBuffer(nullptr)
450 , pendingDownloadDataEmissions(QSharedPointer<QAtomicInt>::create())
451 , pendingDownloadProgressEmissions(QSharedPointer<QAtomicInt>::create())
452 #ifndef QT_NO_SSL
453 , pendingIgnoreAllSslErrors(false)
454 #endif
455
456{
457}
458
459QNetworkReplyHttpImplPrivate::~QNetworkReplyHttpImplPrivate()
460{
461}
462
463/*
464 For a given httpRequest
465 1) If AlwaysNetwork, return
466 2) If we have a cache entry for this url populate headers so the server can return 304
467 3) Calculate if response_is_fresh and if so send the cache and set loadedFromCache to true
468 */
469bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &httpRequest)
470{
471 QNetworkRequest::CacheLoadControl CacheLoadControlAttribute =
472 (QNetworkRequest::CacheLoadControl)request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt();
473 if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) {
474 // If the request does not already specify preferred cache-control
475 // force reload from the network and tell any caching proxy servers to reload too
476 if (!request.rawHeaderList().contains("Cache-Control")) {
477 httpRequest.setHeaderField("Cache-Control", "no-cache");
478 httpRequest.setHeaderField("Pragma", "no-cache");
479 }
480 return false;
481 }
482
483 // The disk cache API does not currently support partial content retrieval.
484 // That is why we don't use the disk cache for any such requests.
485 if (request.hasRawHeader("Range"))
486 return false;
487
488 QAbstractNetworkCache *nc = managerPrivate->networkCache;
489 if (!nc)
490 return false; // no local cache
491
492 QNetworkCacheMetaData metaData = nc->metaData(httpRequest.url());
493 if (!metaData.isValid())
494 return false; // not in cache
495
496 if (!metaData.saveToDisk())
497 return false;
498
499 QNetworkHeadersPrivate cacheHeaders;
500 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
501 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
502
503 it = cacheHeaders.findRawHeader("etag");
504 if (it != cacheHeaders.rawHeaders.constEnd())
505 httpRequest.setHeaderField("If-None-Match", it->second);
506
507 QDateTime lastModified = metaData.lastModified();
508 if (lastModified.isValid())
509 httpRequest.setHeaderField("If-Modified-Since", QNetworkHeadersPrivate::toHttpDate(lastModified));
510
511 it = cacheHeaders.findRawHeader("Cache-Control");
512 if (it != cacheHeaders.rawHeaders.constEnd()) {
513 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
514 if (cacheControl.contains("must-revalidate"))
515 return false;
516 if (cacheControl.contains("no-cache"))
517 return false;
518 }
519
520 QDateTime currentDateTime = QDateTime::currentDateTimeUtc();
521 QDateTime expirationDate = metaData.expirationDate();
522
523 bool response_is_fresh;
524 if (!expirationDate.isValid()) {
525 /*
526 * age_value
527 * is the value of Age: header received by the cache with
528 * this response.
529 * date_value
530 * is the value of the origin server's Date: header
531 * request_time
532 * is the (local) time when the cache made the request
533 * that resulted in this cached response
534 * response_time
535 * is the (local) time when the cache received the
536 * response
537 * now
538 * is the current (local) time
539 */
540 qint64 age_value = 0;
541 it = cacheHeaders.findRawHeader("age");
542 if (it != cacheHeaders.rawHeaders.constEnd())
543 age_value = it->second.toLongLong();
544
545 QDateTime dateHeader;
546 qint64 date_value = 0;
547 it = cacheHeaders.findRawHeader("date");
548 if (it != cacheHeaders.rawHeaders.constEnd()) {
549 dateHeader = QNetworkHeadersPrivate::fromHttpDate(it->second);
550 date_value = dateHeader.toSecsSinceEpoch();
551 }
552
553 qint64 now = currentDateTime.toSecsSinceEpoch();
554 qint64 request_time = now;
555 qint64 response_time = now;
556
557 // Algorithm from RFC 2616 section 13.2.3
558 qint64 apparent_age = qMax<qint64>(0, response_time - date_value);
559 qint64 corrected_received_age = qMax(apparent_age, age_value);
560 qint64 response_delay = response_time - request_time;
561 qint64 corrected_initial_age = corrected_received_age + response_delay;
562 qint64 resident_time = now - response_time;
563 qint64 current_age = corrected_initial_age + resident_time;
564
565 qint64 freshness_lifetime = 0;
566
567 // RFC 2616 13.2.4 Expiration Calculations
568 if (lastModified.isValid() && dateHeader.isValid()) {
569 qint64 diff = lastModified.secsTo(dateHeader);
570 freshness_lifetime = diff / 10;
571 if (httpRequest.headerField("Warning").isEmpty()) {
572 QDateTime dt = currentDateTime.addSecs(current_age);
573 if (currentDateTime.daysTo(dt) > 1)
574 httpRequest.setHeaderField("Warning", "113");
575 }
576 }
577
578 // the cache-saving code below sets the freshness_lifetime with (dateHeader - last_modified) / 10
579 // if "last-modified" is present, or to Expires otherwise
580 response_is_fresh = (freshness_lifetime > current_age);
581 } else {
582 // expiration date was calculated earlier (e.g. when storing object to the cache)
583 response_is_fresh = currentDateTime.secsTo(expirationDate) >= 0;
584 }
585
586 if (!response_is_fresh)
587 return false;
588
589#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
590 qDebug() << "response_is_fresh" << CacheLoadControlAttribute;
591#endif
592 return sendCacheContents(metaData);
593}
594
595QHttpNetworkRequest::Priority QNetworkReplyHttpImplPrivate::convert(const QNetworkRequest::Priority& prio)
596{
597 switch (prio) {
598 case QNetworkRequest::LowPriority:
599 return QHttpNetworkRequest::LowPriority;
600 case QNetworkRequest::HighPriority:
601 return QHttpNetworkRequest::HighPriority;
602 case QNetworkRequest::NormalPriority:
603 default:
604 return QHttpNetworkRequest::NormalPriority;
605 }
606}
607
608void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpRequest)
609{
610 Q_Q(QNetworkReplyHttpImpl);
611
612 QThread *thread = nullptr;
613 if (synchronous) {
614 // A synchronous HTTP request uses its own thread
615 thread = new QThread();
616 thread->setObjectName(QStringLiteral("Qt HTTP synchronous thread"));
617 QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
618 thread->start();
619 } else {
620 // We use the manager-global thread.
621 // At some point we could switch to having multiple threads if it makes sense.
622 thread = managerPrivate->createThread();
623 }
624
625 QUrl url = newHttpRequest.url();
626 httpRequest.setUrl(url);
627 httpRequest.setRedirectCount(newHttpRequest.maximumRedirectsAllowed());
628
629 QString scheme = url.scheme();
630 bool ssl = (scheme == QLatin1String("https")
631 || scheme == QLatin1String("preconnect-https"));
632 q->setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl);
633 httpRequest.setSsl(ssl);
634
635 bool preConnect = (scheme == QLatin1String("preconnect-http")
636 || scheme == QLatin1String("preconnect-https"));
637 httpRequest.setPreConnect(preConnect);
638
639#ifndef QT_NO_NETWORKPROXY
640 QNetworkProxy transparentProxy, cacheProxy;
641
642 // FIXME the proxy stuff should be done in the HTTP thread
643 const auto proxies = managerPrivate->queryProxy(QNetworkProxyQuery(newHttpRequest.url()));
644 for (const QNetworkProxy &p : proxies) {
645 // use the first proxy that works
646 // for non-encrypted connections, any transparent or HTTP proxy
647 // for encrypted, only transparent proxies
648 if (!ssl
649 && (p.capabilities() & QNetworkProxy::CachingCapability)
650 && (p.type() == QNetworkProxy::HttpProxy ||
651 p.type() == QNetworkProxy::HttpCachingProxy)) {
652 cacheProxy = p;
653 transparentProxy = QNetworkProxy::NoProxy;
654 break;
655 }
656 if (p.isTransparentProxy()) {
657 transparentProxy = p;
658 cacheProxy = QNetworkProxy::NoProxy;
659 break;
660 }
661 }
662
663 // check if at least one of the proxies
664 if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
665 cacheProxy.type() == QNetworkProxy::DefaultProxy) {
666 // unsuitable proxies
667 QMetaObject::invokeMethod(q, "_q_error", synchronous ? Qt::DirectConnection : Qt::QueuedConnection,
668 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError),
669 Q_ARG(QString, QNetworkReplyHttpImpl::tr("No suitable proxy found")));
670 QMetaObject::invokeMethod(q, "_q_finished", synchronous ? Qt::DirectConnection : Qt::QueuedConnection);
671 return;
672 }
673#endif
674
675 auto redirectPolicy = QNetworkRequest::NoLessSafeRedirectPolicy;
676 const QVariant value = newHttpRequest.attribute(QNetworkRequest::RedirectPolicyAttribute);
677 if (value.isValid())
678 redirectPolicy = qvariant_cast<QNetworkRequest::RedirectPolicy>(value);
679
680 httpRequest.setRedirectPolicy(redirectPolicy);
681
682 httpRequest.setPriority(convert(newHttpRequest.priority()));
683
684 switch (operation) {
685 case QNetworkAccessManager::GetOperation:
686 httpRequest.setOperation(QHttpNetworkRequest::Get);
687 if (loadFromCacheIfAllowed(httpRequest))
688 return; // no need to send the request! :)
689 break;
690
691 case QNetworkAccessManager::HeadOperation:
692 httpRequest.setOperation(QHttpNetworkRequest::Head);
693 if (loadFromCacheIfAllowed(httpRequest))
694 return; // no need to send the request! :)
695 break;
696
697 case QNetworkAccessManager::PostOperation:
698 invalidateCache();
699 httpRequest.setOperation(QHttpNetworkRequest::Post);
700 createUploadByteDevice();
701 break;
702
703 case QNetworkAccessManager::PutOperation:
704 invalidateCache();
705 httpRequest.setOperation(QHttpNetworkRequest::Put);
706 createUploadByteDevice();
707 break;
708
709 case QNetworkAccessManager::DeleteOperation:
710 invalidateCache();
711 httpRequest.setOperation(QHttpNetworkRequest::Delete);
712 break;
713
714 case QNetworkAccessManager::CustomOperation:
715 invalidateCache(); // for safety reasons, we don't know what the operation does
716 httpRequest.setOperation(QHttpNetworkRequest::Custom);
717 createUploadByteDevice();
718 httpRequest.setCustomVerb(newHttpRequest.attribute(
719 QNetworkRequest::CustomVerbAttribute).toByteArray());
720 break;
721
722 default:
723 break; // can't happen
724 }
725
726 QList<QByteArray> headers = newHttpRequest.rawHeaderList();
727 if (resumeOffset != 0) {
728 const int rangeIndex = headers.indexOf("Range");
729 if (rangeIndex != -1) {
730 // Need to adjust resume offset for user specified range
731
732 headers.removeAt(rangeIndex);
733
734 // We've already verified that requestRange starts with "bytes=", see canResume.
735 QByteArray requestRange = newHttpRequest.rawHeader("Range").mid(6);
736
737 int index = requestRange.indexOf('-');
738
739 quint64 requestStartOffset = requestRange.left(index).toULongLong();
740 quint64 requestEndOffset = requestRange.mid(index + 1).toULongLong();
741
742 // In case an end offset is not given it is skipped from the request range
743 requestRange = "bytes=" + QByteArray::number(resumeOffset + requestStartOffset) +
744 '-' + (requestEndOffset ? QByteArray::number(requestEndOffset) : QByteArray());
745
746 httpRequest.setHeaderField("Range", requestRange);
747 } else {
748 httpRequest.setHeaderField("Range", "bytes=" + QByteArray::number(resumeOffset) + '-');
749 }
750 }
751
752 for (const QByteArray &header : qAsConst(headers))
753 httpRequest.setHeaderField(header, newHttpRequest.rawHeader(header));
754
755 if (newHttpRequest.attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool())
756 httpRequest.setPipeliningAllowed(true);
757
758 if (auto allowed = request.attribute(QNetworkRequest::Http2AllowedAttribute);
759 allowed.isValid() && allowed.canConvert<bool>()) {
760 httpRequest.setHTTP2Allowed(allowed.value<bool>());
761 }
762
763 if (request.attribute(QNetworkRequest::Http2DirectAttribute).toBool()) {
764 // Intentionally mutually exclusive - cannot be both direct and 'allowed'
765 httpRequest.setHTTP2Direct(true);
766 httpRequest.setHTTP2Allowed(false);
767 }
768
769 if (static_cast<QNetworkRequest::LoadControl>
770 (newHttpRequest.attribute(QNetworkRequest::AuthenticationReuseAttribute,
771 QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
772 httpRequest.setWithCredentials(false);
773
774 if (request.attribute(QNetworkRequest::EmitAllUploadProgressSignalsAttribute).toBool())
775 emitAllUploadProgressSignals = true;
776
777 // For internal use/testing
778 auto ignoreDownloadRatio =
779 request.attribute(QNetworkRequest::Attribute(QNetworkRequest::User - 1));
780 if (!ignoreDownloadRatio.isNull() && ignoreDownloadRatio.canConvert<QByteArray>()
781 && ignoreDownloadRatio.toByteArray() == "__qdecompresshelper_ignore_download_ratio") {
782 httpRequest.setIgnoreDecompressionRatio(true);
783 }
784
785 httpRequest.setPeerVerifyName(newHttpRequest.peerVerifyName());
786
787 // Create the HTTP thread delegate
788 QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
789 // Propagate Http/2 settings:
790 delegate->http2Parameters = request.http2Configuration();
791
792 // For the synchronous HTTP, this is the normal way the delegate gets deleted
793 // For the asynchronous HTTP this is a safety measure, the delegate deletes itself when HTTP is finished
794 QObject::connect(thread, SIGNAL(finished()), delegate, SLOT(deleteLater()));
795
796 // Set the properties it needs
797 delegate->httpRequest = httpRequest;
798#ifndef QT_NO_NETWORKPROXY
799 delegate->cacheProxy = cacheProxy;
800 delegate->transparentProxy = transparentProxy;
801#endif
802 delegate->ssl = ssl;
803#ifndef QT_NO_SSL
804 if (ssl)
805 delegate->incomingSslConfiguration.reset(new QSslConfiguration(newHttpRequest.sslConfiguration()));
806#endif
807
808 // Do we use synchronous HTTP?
809 delegate->synchronous = synchronous;
810
811 // The authentication manager is used to avoid the BlockingQueuedConnection communication
812 // from HTTP thread to user thread in some cases.
813 delegate->authenticationManager = managerPrivate->authenticationManager;
814
815 if (!synchronous) {
816 // Tell our zerocopy policy to the delegate
817 QVariant downloadBufferMaximumSizeAttribute = newHttpRequest.attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute);
818 if (downloadBufferMaximumSizeAttribute.isValid()) {
819 delegate->downloadBufferMaximumSize = downloadBufferMaximumSizeAttribute.toLongLong();
820 } else {
821 // If there is no MaximumDownloadBufferSizeAttribute set (which is for the majority
822 // of QNetworkRequest) then we can assume we'll do it anyway for small HTTP replies.
823 // This helps with performance and memory fragmentation.
824 delegate->downloadBufferMaximumSize = 128*1024;
825 }
826
827
828 // These atomic integers are used for signal compression
829 delegate->pendingDownloadData = pendingDownloadDataEmissions;
830 delegate->pendingDownloadProgress = pendingDownloadProgressEmissions;
831
832 // Connect the signals of the delegate to us
833 QObject::connect(delegate, SIGNAL(downloadData(QByteArray)),
834 q, SLOT(replyDownloadData(QByteArray)),
835 Qt::QueuedConnection);
836 QObject::connect(delegate, SIGNAL(downloadFinished()),
837 q, SLOT(replyFinished()),
838 Qt::QueuedConnection);
839 QObject::connect(delegate, SIGNAL(downloadMetaData(QList<QPair<QByteArray,QByteArray> >,
840 int, QString, bool,
841 QSharedPointer<char>, qint64, qint64,
842 bool)),
843 q, SLOT(replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,
844 int, QString, bool,
845 QSharedPointer<char>, qint64, qint64, bool)),
846 Qt::QueuedConnection);
847 QObject::connect(delegate, SIGNAL(downloadProgress(qint64,qint64)),
848 q, SLOT(replyDownloadProgressSlot(qint64,qint64)),
849 Qt::QueuedConnection);
850 QObject::connect(delegate, SIGNAL(error(QNetworkReply::NetworkError,QString)),
851 q, SLOT(httpError(QNetworkReply::NetworkError,QString)),
852 Qt::QueuedConnection);
853 QObject::connect(delegate, SIGNAL(redirected(QUrl,int,int)),
854 q, SLOT(onRedirected(QUrl,int,int)),
855 Qt::QueuedConnection);
856
857 QObject::connect(q, SIGNAL(redirectAllowed()), q, SLOT(followRedirect()),
858 Qt::QueuedConnection);
859
860#ifndef QT_NO_SSL
861 QObject::connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)),
862 q, SLOT(replySslConfigurationChanged(QSslConfiguration)),
863 Qt::QueuedConnection);
864#endif
865 // Those need to report back, therefore BlockingQueuedConnection
866 QObject::connect(delegate, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
867 q, SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
868 Qt::BlockingQueuedConnection);
869#ifndef QT_NO_NETWORKPROXY
870 QObject::connect(delegate, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
871 q, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
872 Qt::BlockingQueuedConnection);
873#endif
874#ifndef QT_NO_SSL
875 QObject::connect(delegate, SIGNAL(encrypted()), q, SLOT(replyEncrypted()),
876 Qt::BlockingQueuedConnection);
877 QObject::connect(delegate, SIGNAL(sslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
878 q, SLOT(replySslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
879 Qt::BlockingQueuedConnection);
880 QObject::connect(delegate, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
881 q, SLOT(replyPreSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator*)),
882 Qt::BlockingQueuedConnection);
883#endif
884 // This signal we will use to start the request.
885 QObject::connect(q, SIGNAL(startHttpRequest()), delegate, SLOT(startRequest()));
886 QObject::connect(q, SIGNAL(abortHttpRequest()), delegate, SLOT(abortRequest()));
887
888 // To throttle the connection.
889 QObject::connect(q, SIGNAL(readBufferSizeChanged(qint64)), delegate, SLOT(readBufferSizeChanged(qint64)));
890 QObject::connect(q, SIGNAL(readBufferFreed(qint64)), delegate, SLOT(readBufferFreed(qint64)));
891
892 if (uploadByteDevice) {
893 QNonContiguousByteDeviceThreadForwardImpl *forwardUploadDevice =
894 new QNonContiguousByteDeviceThreadForwardImpl(uploadByteDevice->atEnd(), uploadByteDevice->size());
895 forwardUploadDevice->setParent(delegate); // needed to make sure it is moved on moveToThread()
896 delegate->httpRequest.setUploadByteDevice(forwardUploadDevice);
897
898 // If the device in the user thread claims it has more data, keep the flow to HTTP thread going
899 QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
900 q, SLOT(uploadByteDeviceReadyReadSlot()),
901 Qt::QueuedConnection);
902
903 // From user thread to http thread:
904 QObject::connect(q, SIGNAL(haveUploadData(qint64,QByteArray,bool,qint64)),
905 forwardUploadDevice, SLOT(haveDataSlot(qint64,QByteArray,bool,qint64)), Qt::QueuedConnection);
906 QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
907 forwardUploadDevice, SIGNAL(readyRead()),
908 Qt::QueuedConnection);
909
910 // From http thread to user thread:
911 QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)),
912 q, SLOT(wantUploadDataSlot(qint64)));
913 QObject::connect(forwardUploadDevice,SIGNAL(processedData(qint64,qint64)),
914 q, SLOT(sentUploadDataSlot(qint64,qint64)));
915 QObject::connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
916 q, SLOT(resetUploadDataSlot(bool*)),
917 Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
918 }
919 } else if (synchronous) {
920 QObject::connect(q, SIGNAL(startHttpRequestSynchronously()), delegate, SLOT(startRequestSynchronously()), Qt::BlockingQueuedConnection);
921
922 if (uploadByteDevice) {
923 // For the synchronous HTTP use case the use thread (this one here) is blocked
924 // so we cannot use the asynchronous upload architecture.
925 // We therefore won't use the QNonContiguousByteDeviceThreadForwardImpl but directly
926 // use the uploadByteDevice provided to us by the QNetworkReplyImpl.
927 // The code that is in start() makes sure it is safe to use from a thread
928 // since it only wraps a QRingBuffer
929 delegate->httpRequest.setUploadByteDevice(uploadByteDevice.data());
930 }
931 }
932
933
934 // Move the delegate to the http thread
935 delegate->moveToThread(thread);
936 // This call automatically moves the uploadDevice too for the asynchronous case.
937
938 // Prepare timers for progress notifications
939 downloadProgressSignalChoke.start();
940 uploadProgressSignalChoke.invalidate();
941
942 // Send an signal to the delegate so it starts working in the other thread
943 if (synchronous) {
944 emit q->startHttpRequestSynchronously(); // This one is BlockingQueuedConnection, so it will return when all work is done
945
946 if (delegate->incomingErrorCode != QNetworkReply::NoError) {
947 replyDownloadMetaData
948 (delegate->incomingHeaders,
949 delegate->incomingStatusCode,
950 delegate->incomingReasonPhrase,
951 delegate->isPipeliningUsed,
952 QSharedPointer<char>(),
953 delegate->incomingContentLength,
954 delegate->removedContentLength,
955 delegate->isHttp2Used);
956 replyDownloadData(delegate->synchronousDownloadData);
957 httpError(delegate->incomingErrorCode, delegate->incomingErrorDetail);
958 } else {
959 replyDownloadMetaData
960 (delegate->incomingHeaders,
961 delegate->incomingStatusCode,
962 delegate->incomingReasonPhrase,
963 delegate->isPipeliningUsed,
964 QSharedPointer<char>(),
965 delegate->incomingContentLength,
966 delegate->removedContentLength,
967 delegate->isHttp2Used);
968 replyDownloadData(delegate->synchronousDownloadData);
969 }
970
971 thread->quit();
972 thread->wait(QDeadlineTimer(5000));
973 if (thread->isFinished())
974 delete thread;
975 else
976 QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
977
978 finished();
979 } else {
980 emit q->startHttpRequest(); // Signal to the HTTP thread and go back to user.
981 }
982}
983
984void QNetworkReplyHttpImplPrivate::invalidateCache()
985{
986 QAbstractNetworkCache *nc = managerPrivate->networkCache;
987 if (nc)
988 nc->remove(httpRequest.url());
989}
990
991void QNetworkReplyHttpImplPrivate::initCacheSaveDevice()
992{
993 Q_Q(QNetworkReplyHttpImpl);
994
995 // The disk cache does not support partial content, so don't even try to
996 // save any such content into the cache.
997 if (q->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 206) {
998 cacheEnabled = false;
999 return;
1000 }
1001
1002 // save the meta data
1003 QNetworkCacheMetaData metaData;
1004 metaData.setUrl(url);
1005 metaData = fetchCacheMetaData(metaData);
1006
1007 // save the redirect request also in the cache
1008 QVariant redirectionTarget = q->attribute(QNetworkRequest::RedirectionTargetAttribute);
1009 if (redirectionTarget.isValid()) {
1010 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
1011 attributes.insert(QNetworkRequest::RedirectionTargetAttribute, redirectionTarget);
1012 metaData.setAttributes(attributes);
1013 }
1014
1015 cacheSaveDevice = managerPrivate->networkCache->prepare(metaData);
1016
1017 if (cacheSaveDevice)
1018 q->connect(cacheSaveDevice, SIGNAL(aboutToClose()), SLOT(_q_cacheSaveDeviceAboutToClose()));
1019
1020 if (!cacheSaveDevice || (cacheSaveDevice && !cacheSaveDevice->isOpen())) {
1021 if (Q_UNLIKELY(cacheSaveDevice && !cacheSaveDevice->isOpen()))
1022 qCritical("QNetworkReplyImpl: network cache returned a device that is not open -- "
1023 "class %s probably needs to be fixed",
1024 managerPrivate->networkCache->metaObject()->className());
1025
1026 managerPrivate->networkCache->remove(url);
1027 cacheSaveDevice = nullptr;
1028 cacheEnabled = false;
1029 }
1030}
1031
1032void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d)
1033{
1034 Q_Q(QNetworkReplyHttpImpl);
1035
1036 // If we're closed just ignore this data
1037 if (!q->isOpen())
1038 return;
1039
1040 if (cacheEnabled && isCachingAllowed() && !cacheSaveDevice)
1041 initCacheSaveDevice();
1042
1043 // This is going to look a little strange. When downloading data while a
1044 // HTTP redirect is happening (and enabled), we write the redirect
1045 // response to the cache. However, we do not append it to our internal
1046 // buffer as that will contain the response data only for the final
1047 // response
1048 if (cacheSaveDevice)
1049 cacheSaveDevice->write(d);
1050
1051 if (!isHttpRedirectResponse()) {
1052 buffer.append(d);
1053 bytesDownloaded += d.size();
1054 setupTransferTimeout();
1055 }
1056 bytesBuffered += d.size();
1057
1058 int pendingSignals = pendingDownloadDataEmissions->fetchAndSubAcquire(1) - 1;
1059 if (pendingSignals > 0) {
1060 // Some more signal emissions to this slot are pending.
1061 // Instead of writing the downstream data, we wait
1062 // and do it in the next call we get
1063 // (signal comppression)
1064 return;
1065 }
1066
1067 if (isHttpRedirectResponse())
1068 return;
1069
1070 QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
1071
1072 emit q->readyRead();
1073 // emit readyRead before downloadProgress incase this will cause events to be
1074 // processed and we get into a recursive call (as in QProgressDialog).
1075 if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
1076 downloadProgressSignalChoke.restart();
1077 emit q->downloadProgress(bytesDownloaded,
1078 totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
1079 }
1080
1081}
1082
1083void QNetworkReplyHttpImplPrivate::replyFinished()
1084{
1085 // We are already loading from cache, we still however
1086 // got this signal because it was posted already
1087 if (loadingFromCache)
1088 return;
1089
1090 finished();
1091}
1092
1093QNetworkAccessManager::Operation QNetworkReplyHttpImplPrivate::getRedirectOperation(QNetworkAccessManager::Operation currentOp, int httpStatus)
1094{
1095 // HTTP status code can be used to decide if we can redirect with a GET
1096 // operation or not. See http://www.ietf.org/rfc/rfc2616.txt [Sec 10.3] for
1097 // more details
1098
1099 // We MUST keep using the verb that was used originally when being redirected with 307 or 308.
1100 if (httpStatus == 307 || httpStatus == 308)
1101 return currentOp;
1102
1103 switch (currentOp) {
1104 case QNetworkAccessManager::HeadOperation:
1105 return QNetworkAccessManager::HeadOperation;
1106 default:
1107 break;
1108 }
1109 // Use GET for everything else.
1110 return QNetworkAccessManager::GetOperation;
1111}
1112
1113bool QNetworkReplyHttpImplPrivate::isHttpRedirectResponse() const
1114{
1115 return httpRequest.isFollowRedirects() && QHttpNetworkReply::isHttpRedirect(statusCode);
1116}
1117
1118QNetworkRequest QNetworkReplyHttpImplPrivate::createRedirectRequest(const QNetworkRequest &originalRequest,
1119 const QUrl &url,
1120 int maxRedirectsRemaining)
1121{
1122 QNetworkRequest newRequest(originalRequest);
1123 newRequest.setUrl(url);
1124 newRequest.setMaximumRedirectsAllowed(maxRedirectsRemaining);
1125
1126 return newRequest;
1127}
1128
1129void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int httpStatus, int maxRedirectsRemaining)
1130{
1131 Q_Q(QNetworkReplyHttpImpl);
1132 Q_ASSERT(manager);
1133 Q_ASSERT(managerPrivate);
1134
1135 if (isFinished)
1136 return;
1137
1138 const QString schemeBefore(url.scheme());
1139 if (httpRequest.isFollowRedirects()) // update the reply's url as it could've changed
1140 url = redirectUrl;
1141
1142 if (managerPrivate->stsEnabled && managerPrivate->stsCache.isKnownHost(url)) {
1143 // RFC6797, 8.3:
1144 // The UA MUST replace the URI scheme with "https" [RFC2818],
1145 // and if the URI contains an explicit port component of "80",
1146 // then the UA MUST convert the port component to be "443", or
1147 // if the URI contains an explicit port component that is not
1148 // equal to "80", the port component value MUST be preserved;
1149 // otherwise, if the URI does not contain an explicit port
1150 // component, the UA MUST NOT add one.
1151 url.setScheme(QLatin1String("https"));
1152 if (url.port() == 80)
1153 url.setPort(443);
1154 }
1155
1156 const bool isLessSafe = schemeBefore == QLatin1String("https")
1157 && url.scheme() == QLatin1String("http");
1158 if (httpRequest.redirectPolicy() == QNetworkRequest::NoLessSafeRedirectPolicy
1159 && isLessSafe) {
1160 error(QNetworkReply::InsecureRedirectError,
1161 QCoreApplication::translate("QHttp", "Insecure redirect"));
1162 return;
1163 }
1164
1165 redirectRequest = createRedirectRequest(originalRequest, url, maxRedirectsRemaining);
1166 operation = getRedirectOperation(operation, httpStatus);
1167
1168 // Clear stale headers, the relevant ones get set again later
1169 httpRequest.clearHeaders();
1170 if (operation == QNetworkAccessManager::GetOperation
1171 || operation == QNetworkAccessManager::HeadOperation) {
1172 // possibly changed from not-GET/HEAD to GET/HEAD, make sure to get rid of upload device
1173 uploadByteDevice.reset();
1174 uploadByteDevicePosition = 0;
1175 if (outgoingData) {
1176 QObject::disconnect(outgoingData, SIGNAL(readyRead()), q,
1177 SLOT(_q_bufferOutgoingData()));
1178 QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q,
1179 SLOT(_q_bufferOutgoingDataFinished()));
1180 }
1181 outgoingData = nullptr;
1182 outgoingDataBuffer.reset();
1183 // We need to explicitly unset these headers so they're not reapplied to the httpRequest
1184 redirectRequest.setHeader(QNetworkRequest::ContentLengthHeader, QVariant());
1185 redirectRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant());
1186 }
1187
1188 if (const QNetworkCookieJar *const cookieJar = manager->cookieJar()) {
1189 auto cookies = cookieJar->cookiesForUrl(url);
1190 if (!cookies.empty()) {
1191 redirectRequest.setHeader(QNetworkRequest::KnownHeaders::CookieHeader,
1192 QVariant::fromValue(cookies));
1193 }
1194 }
1195
1196 if (httpRequest.redirectPolicy() != QNetworkRequest::UserVerifiedRedirectPolicy)
1197 followRedirect();
1198
1199 emit q->redirected(url);
1200}
1201
1202void QNetworkReplyHttpImplPrivate::followRedirect()
1203{
1204 Q_Q(QNetworkReplyHttpImpl);
1205 Q_ASSERT(managerPrivate);
1206
1207 rawHeaders.clear();
1208 cookedHeaders.clear();
1209
1210 if (managerPrivate->thread)
1211 managerPrivate->thread->disconnect();
1212
1213 QMetaObject::invokeMethod(
1214 q, [this]() { postRequest(redirectRequest); }, Qt::QueuedConnection);
1215}
1216
1217void QNetworkReplyHttpImplPrivate::checkForRedirect(const int statusCode)
1218{
1219 Q_Q(QNetworkReplyHttpImpl);
1220 switch (statusCode) {
1221 case 301: // Moved Permanently
1222 case 302: // Found
1223 case 303: // See Other
1224 case 307: // Temporary Redirect
1225 case 308: // Permanent Redirect
1226 // What do we do about the caching of the HTML note?
1227 // The response to a 303 MUST NOT be cached, while the response to
1228 // all of the others is cacheable if the headers indicate it to be
1229 QByteArray header = q->rawHeader("location");
1230 QUrl url = QUrl(QString::fromUtf8(header));
1231 if (!url.isValid())
1232 url = QUrl(QLatin1String(header));
1233 q->setAttribute(QNetworkRequest::RedirectionTargetAttribute, url);
1234 }
1235}
1236
1237void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QList<QPair<QByteArray,QByteArray> > &hm,
1238 int sc, const QString &rp, bool pu,
1239 QSharedPointer<char> db,
1240 qint64 contentLength,
1241 qint64 removedContentLength,
1242 bool h2Used)
1243{
1244 Q_Q(QNetworkReplyHttpImpl);
1245 Q_UNUSED(contentLength);
1246
1247 statusCode = sc;
1248 reasonPhrase = rp;
1249
1250#ifndef QT_NO_SSL
1251 // We parse this header only if we're using secure transport:
1252 //
1253 // RFC6797, 8.1
1254 // If an HTTP response is received over insecure transport, the UA MUST
1255 // ignore any present STS header field(s).
1256 if (url.scheme() == QLatin1String("https") && managerPrivate->stsEnabled)
1257 managerPrivate->stsCache.updateFromHeaders(hm, url);
1258#endif
1259 // Download buffer
1260 if (!db.isNull()) {
1261 downloadBufferPointer = db;
1262 downloadZerocopyBuffer = downloadBufferPointer.data();
1263 downloadBufferCurrentSize = 0;
1264 q->setAttribute(QNetworkRequest::DownloadBufferAttribute, QVariant::fromValue<QSharedPointer<char> > (downloadBufferPointer));
1265 }
1266
1267 q->setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, pu);
1268 q->setAttribute(QNetworkRequest::Http2WasUsedAttribute, h2Used);
1269
1270 // reconstruct the HTTP header
1271 QList<QPair<QByteArray, QByteArray> > headerMap = hm;
1272 QList<QPair<QByteArray, QByteArray> >::ConstIterator it = headerMap.constBegin(),
1273 end = headerMap.constEnd();
1274 for (; it != end; ++it) {
1275 QByteArray value = q->rawHeader(it->first);
1276
1277 // Reset any previous "location" header set in the reply. In case of
1278 // redirects, we don't want to 'append' multiple location header values,
1279 // rather we keep only the latest one
1280 if (it->first.toLower() == "location")
1281 value.clear();
1282
1283 if (!value.isEmpty()) {
1284 // Why are we appending values for headers which are already
1285 // present?
1286 if (it->first.compare("set-cookie", Qt::CaseInsensitive) == 0)
1287 value += '\n';
1288 else
1289 value += ", ";
1290 }
1291 value += it->second;
1292 q->setRawHeader(it->first, value);
1293 }
1294
1295 q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1296 q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
1297 if (removedContentLength != -1)
1298 q->setAttribute(QNetworkRequest::OriginalContentLengthAttribute, removedContentLength);
1299
1300 // is it a redirection?
1301 if (!isHttpRedirectResponse())
1302 checkForRedirect(statusCode);
1303
1304 if (statusCode >= 500 && statusCode < 600) {
1305 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1306 if (nc) {
1307 QNetworkCacheMetaData metaData = nc->metaData(httpRequest.url());
1308 QNetworkHeadersPrivate cacheHeaders;
1309 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
1310 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
1311 it = cacheHeaders.findRawHeader("Cache-Control");
1312 bool mustReValidate = false;
1313 if (it != cacheHeaders.rawHeaders.constEnd()) {
1314 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
1315 if (cacheControl.contains("must-revalidate"))
1316 mustReValidate = true;
1317 }
1318 if (!mustReValidate && sendCacheContents(metaData))
1319 return;
1320 }
1321 }
1322
1323 if (statusCode == 304) {
1324#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1325 qDebug() << "Received a 304 from" << request.url();
1326#endif
1327 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1328 if (nc) {
1329 QNetworkCacheMetaData oldMetaData = nc->metaData(httpRequest.url());
1330 QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData);
1331 if (oldMetaData != metaData)
1332 nc->updateMetaData(metaData);
1333 if (sendCacheContents(metaData))
1334 return;
1335 }
1336 }
1337
1338
1339 if (statusCode != 304 && statusCode != 303) {
1340 if (!isCachingEnabled())
1341 setCachingEnabled(true);
1342 }
1343
1344 _q_metaDataChanged();
1345}
1346
1347void QNetworkReplyHttpImplPrivate::replyDownloadProgressSlot(qint64 bytesReceived, qint64 bytesTotal)
1348{
1349 Q_Q(QNetworkReplyHttpImpl);
1350
1351 // If we're closed just ignore this data
1352 if (!q->isOpen())
1353 return;
1354
1355 // we can be sure here that there is a download buffer
1356
1357 int pendingSignals = (int)pendingDownloadProgressEmissions->fetchAndAddAcquire(-1) - 1;
1358 if (pendingSignals > 0) {
1359 // Let's ignore this signal and look at the next one coming in
1360 // (signal comppression)
1361 return;
1362 }
1363
1364 if (!q->isOpen())
1365 return;
1366
1367 if (cacheEnabled && isCachingAllowed() && bytesReceived == bytesTotal) {
1368 // Write everything in one go if we use a download buffer. might be more performant.
1369 initCacheSaveDevice();
1370 // need to check again if cache enabled and device exists
1371 if (cacheSaveDevice && cacheEnabled)
1372 cacheSaveDevice->write(downloadZerocopyBuffer, bytesTotal);
1373 // FIXME where is it closed?
1374 }
1375
1376 if (isHttpRedirectResponse())
1377 return;
1378
1379 bytesDownloaded = bytesReceived;
1380 setupTransferTimeout();
1381
1382 downloadBufferCurrentSize = bytesReceived;
1383
1384 // Only emit readyRead when actual data is there
1385 // emit readyRead before downloadProgress incase this will cause events to be
1386 // processed and we get into a recursive call (as in QProgressDialog).
1387 if (bytesDownloaded > 0)
1388 emit q->readyRead();
1389 if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
1390 downloadProgressSignalChoke.restart();
1391 emit q->downloadProgress(bytesDownloaded, bytesTotal);
1392 }
1393}
1394
1395void QNetworkReplyHttpImplPrivate::httpAuthenticationRequired(const QHttpNetworkRequest &request,
1396 QAuthenticator *auth)
1397{
1398 managerPrivate->authenticationRequired(auth, q_func(), synchronous, url, &urlForLastAuthentication, request.withCredentials());
1399}
1400
1401#ifndef QT_NO_NETWORKPROXY
1402void QNetworkReplyHttpImplPrivate::proxyAuthenticationRequired(const QNetworkProxy &proxy,
1403 QAuthenticator *authenticator)
1404{
1405 managerPrivate->proxyAuthenticationRequired(request.url(), proxy, synchronous, authenticator, &lastProxyAuthentication);
1406}
1407#endif
1408
1409void QNetworkReplyHttpImplPrivate::httpError(QNetworkReply::NetworkError errorCode,
1410 const QString &errorString)
1411{
1412#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1413 qDebug() << "http error!" << errorCode << errorString;
1414#endif
1415
1416 // FIXME?
1417 error(errorCode, errorString);
1418}
1419
1420#ifndef QT_NO_SSL
1421void QNetworkReplyHttpImplPrivate::replyEncrypted()
1422{
1423 Q_Q(QNetworkReplyHttpImpl);
1424 emit q->encrypted();
1425}
1426
1427void QNetworkReplyHttpImplPrivate::replySslErrors(
1428 const QList<QSslError> &list, bool *ignoreAll, QList<QSslError> *toBeIgnored)
1429{
1430 Q_Q(QNetworkReplyHttpImpl);
1431 emit q->sslErrors(list);
1432 // Check if the callback set any ignore and return this here to http thread
1433 if (pendingIgnoreAllSslErrors)
1434 *ignoreAll = true;
1435 if (!pendingIgnoreSslErrorsList.isEmpty())
1436 *toBeIgnored = pendingIgnoreSslErrorsList;
1437}
1438
1439void QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfiguration &newSslConfiguration)
1440{
1441 // Receiving the used SSL configuration from the HTTP thread
1442 if (sslConfiguration.data())
1443 *sslConfiguration = newSslConfiguration;
1444 else
1445 sslConfiguration.reset(new QSslConfiguration(newSslConfiguration));
1446}
1447
1448void QNetworkReplyHttpImplPrivate::replyPreSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator *authenticator)
1449{
1450 Q_Q(QNetworkReplyHttpImpl);
1451 emit q->preSharedKeyAuthenticationRequired(authenticator);
1452}
1453#endif
1454
1455// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1456void QNetworkReplyHttpImplPrivate::resetUploadDataSlot(bool *r)
1457{
1458 *r = uploadByteDevice->reset();
1459 if (*r) {
1460 // reset our own position which is used for the inter-thread communication
1461 uploadByteDevicePosition = 0;
1462 }
1463}
1464
1465// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1466void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 pos, qint64 amount)
1467{
1468 if (!uploadByteDevice) // uploadByteDevice is no longer available
1469 return;
1470
1471 if (uploadByteDevicePosition + amount != pos) {
1472 // Sanity check, should not happen.
1473 error(QNetworkReply::UnknownNetworkError, QString());
1474 return;
1475 }
1476 uploadByteDevice->advanceReadPointer(amount);
1477 uploadByteDevicePosition += amount;
1478}
1479
1480// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1481void QNetworkReplyHttpImplPrivate::wantUploadDataSlot(qint64 maxSize)
1482{
1483 Q_Q(QNetworkReplyHttpImpl);
1484
1485 if (!uploadByteDevice) // uploadByteDevice is no longer available
1486 return;
1487
1488 // call readPointer
1489 qint64 currentUploadDataLength = 0;
1490 char *data = const_cast<char*>(uploadByteDevice->readPointer(maxSize, currentUploadDataLength));
1491
1492 if (currentUploadDataLength == 0) {
1493 uploadDeviceChoking = true;
1494 // No bytes from upload byte device. There will be bytes later, it will emit readyRead()
1495 // and our uploadByteDeviceReadyReadSlot() is called.
1496 return;
1497 } else {
1498 uploadDeviceChoking = false;
1499 }
1500
1501 // Let's make a copy of this data
1502 QByteArray dataArray(data, currentUploadDataLength);
1503
1504 // Communicate back to HTTP thread
1505 emit q->haveUploadData(uploadByteDevicePosition, dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
1506}
1507
1508void QNetworkReplyHttpImplPrivate::uploadByteDeviceReadyReadSlot()
1509{
1510 // Start the flow between this thread and the HTTP thread again by triggering a upload.
1511 // However only do this when we were choking before, else the state in
1512 // QNonContiguousByteDeviceThreadForwardImpl gets messed up.
1513 if (uploadDeviceChoking) {
1514 uploadDeviceChoking = false;
1515 wantUploadDataSlot(1024);
1516 }
1517}
1518
1519
1520/*
1521 A simple web page that can be used to test us: http://www.procata.com/cachetest/
1522 */
1523bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData &metaData)
1524{
1525 Q_Q(QNetworkReplyHttpImpl);
1526
1527 setCachingEnabled(false);
1528 if (!metaData.isValid())
1529 return false;
1530
1531 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1532 Q_ASSERT(nc);
1533 QIODevice *contents = nc->data(url);
1534 if (!contents) {
1535#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1536 qDebug() << "Cannot send cache, the contents are 0" << url;
1537#endif
1538 return false;
1539 }
1540 contents->setParent(q);
1541
1542 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
1543 int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt();
1544 if (status < 100)
1545 status = 200; // fake it
1546
1547 statusCode = status;
1548
1549 q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
1550 q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
1551 q->setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
1552
1553 QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders();
1554 QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(),
1555 end = rawHeaders.constEnd();
1556 QUrl redirectUrl;
1557 for ( ; it != end; ++it) {
1558 if (httpRequest.isFollowRedirects() &&
1559 !it->first.compare("location", Qt::CaseInsensitive))
1560 redirectUrl = QUrl::fromEncoded(it->second);
1561 setRawHeader(it->first, it->second);
1562 }
1563
1564 if (!isHttpRedirectResponse())
1565 checkForRedirect(status);
1566
1567 cacheLoadDevice = contents;
1568 q->connect(cacheLoadDevice, SIGNAL(readyRead()), SLOT(_q_cacheLoadReadyRead()));
1569 q->connect(cacheLoadDevice, SIGNAL(readChannelFinished()), SLOT(_q_cacheLoadReadyRead()));
1570
1571 // This needs to be emitted in the event loop because it can be reached at
1572 // the direct code path of qnam.get(...) before the user has a chance
1573 // to connect any signals.
1574 QMetaObject::invokeMethod(q, "_q_metaDataChanged", Qt::QueuedConnection);
1575 QMetaObject::invokeMethod(q, "_q_cacheLoadReadyRead", Qt::QueuedConnection);
1576
1577
1578#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1579 qDebug() << "Successfully sent cache:" << url << contents->size() << "bytes";
1580#endif
1581
1582 // Do redirect processing
1583 if (httpRequest.isFollowRedirects() && QHttpNetworkReply::isHttpRedirect(status)) {
1584 QMetaObject::invokeMethod(q, "onRedirected", Qt::QueuedConnection,
1585 Q_ARG(QUrl, redirectUrl),
1586 Q_ARG(int, status),
1587 Q_ARG(int, httpRequest.redirectCount() - 1));
1588 }
1589
1590 // Set the following flag so we can ignore some signals from HTTP thread
1591 // that would still come
1592 loadingFromCache = true;
1593 return true;
1594}
1595
1596QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const
1597{
1598 Q_Q(const QNetworkReplyHttpImpl);
1599
1600 QNetworkCacheMetaData metaData = oldMetaData;
1601
1602 QNetworkHeadersPrivate cacheHeaders;
1603 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
1604 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
1605
1606 const QList<QByteArray> newHeaders = q->rawHeaderList();
1607 for (QByteArray header : newHeaders) {
1608 QByteArray originalHeader = header;
1609 header = header.toLower();
1610 bool hop_by_hop =
1611 (header == "connection"
1612 || header == "keep-alive"
1613 || header == "proxy-authenticate"
1614 || header == "proxy-authorization"
1615 || header == "te"
1616 || header == "trailers"
1617 || header == "transfer-encoding"
1618 || header == "upgrade");
1619 if (hop_by_hop)
1620 continue;
1621
1622 if (header == "set-cookie")
1623 continue;
1624
1625 // for 4.6.0, we were planning to not store the date header in the
1626 // cached resource; through that we planned to reduce the number
1627 // of writes to disk when using a QNetworkDiskCache (i.e. don't
1628 // write to disk when only the date changes).
1629 // However, without the date we cannot calculate the age of the page
1630 // anymore.
1631 //if (header == "date")
1632 //continue;
1633
1634 // Don't store Warning 1xx headers
1635 if (header == "warning") {
1636 QByteArray v = q->rawHeader(header);
1637 if (v.length() == 3
1638 && v[0] == '1'
1639 && v[1] >= '0' && v[1] <= '9'
1640 && v[2] >= '0' && v[2] <= '9')
1641 continue;
1642 }
1643
1644 it = cacheHeaders.findRawHeader(header);
1645 if (it != cacheHeaders.rawHeaders.constEnd()) {
1646 // Match the behavior of Firefox and assume Cache-Control: "no-transform"
1647 if (header == "content-encoding"
1648 || header == "content-range"
1649 || header == "content-type")
1650 continue;
1651 }
1652
1653 // IIS has been known to send "Content-Length: 0" on 304 responses, so
1654 // ignore this too
1655 if (header == "content-length" && statusCode == 304)
1656 continue;
1657
1658#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1659 QByteArray n = q->rawHeader(header);
1660 QByteArray o;
1661 if (it != cacheHeaders.rawHeaders.constEnd())
1662 o = (*it).second;
1663 if (n != o && header != "date") {
1664 qDebug() << "replacing" << header;
1665 qDebug() << "new" << n;
1666 qDebug() << "old" << o;
1667 }
1668#endif
1669 cacheHeaders.setRawHeader(originalHeader, q->rawHeader(header));
1670 }
1671 metaData.setRawHeaders(cacheHeaders.rawHeaders);
1672
1673 bool checkExpired = true;
1674
1675 QHash<QByteArray, QByteArray> cacheControl;
1676 it = cacheHeaders.findRawHeader("Cache-Control");
1677 if (it != cacheHeaders.rawHeaders.constEnd()) {
1678 cacheControl = parseHttpOptionHeader(it->second);
1679 QByteArray maxAge = cacheControl.value("max-age");
1680 if (!maxAge.isEmpty()) {
1681 checkExpired = false;
1682 QDateTime dt = QDateTime::currentDateTimeUtc();
1683 dt = dt.addSecs(maxAge.toInt());
1684 metaData.setExpirationDate(dt);
1685 }
1686 }
1687 if (checkExpired) {
1688 it = cacheHeaders.findRawHeader("expires");
1689 if (it != cacheHeaders.rawHeaders.constEnd()) {
1690 QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(it->second);
1691 metaData.setExpirationDate(expiredDateTime);
1692 }
1693 }
1694
1695 it = cacheHeaders.findRawHeader("last-modified");
1696 if (it != cacheHeaders.rawHeaders.constEnd())
1697 metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(it->second));
1698
1699 bool canDiskCache;
1700 // only cache GET replies by default, all other replies (POST, PUT, DELETE)
1701 // are not cacheable by default (according to RFC 2616 section 9)
1702 if (httpRequest.operation() == QHttpNetworkRequest::Get) {
1703
1704 canDiskCache = true;
1705 // HTTP/1.1. Check the Cache-Control header
1706 if (cacheControl.contains("no-store"))
1707 canDiskCache = false;
1708
1709 // responses to POST might be cacheable
1710 } else if (httpRequest.operation() == QHttpNetworkRequest::Post) {
1711
1712 canDiskCache = false;
1713 // some pages contain "expires:" and "cache-control: no-cache" field,
1714 // so we only might cache POST requests if we get "cache-control: max-age ..."
1715 if (cacheControl.contains("max-age"))
1716 canDiskCache = true;
1717
1718 // responses to PUT and DELETE are not cacheable
1719 } else {
1720 canDiskCache = false;
1721 }
1722
1723 metaData.setSaveToDisk(canDiskCache);
1724 QNetworkCacheMetaData::AttributesMap attributes;
1725 if (statusCode != 304) {
1726 // update the status code
1727 attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1728 attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
1729 } else {
1730 // this is a redirection, keep the attributes intact
1731 attributes = oldMetaData.attributes();
1732 }
1733 metaData.setAttributes(attributes);
1734 return metaData;
1735}
1736
1737bool QNetworkReplyHttpImplPrivate::canResume() const
1738{
1739 Q_Q(const QNetworkReplyHttpImpl);
1740
1741 // Only GET operation supports resuming.
1742 if (operation != QNetworkAccessManager::GetOperation)
1743 return false;
1744
1745 // Can only resume if server/resource supports Range header.
1746 QByteArray acceptRangesheaderName("Accept-Ranges");
1747 if (!q->hasRawHeader(acceptRangesheaderName) || q->rawHeader(acceptRangesheaderName) == "none")
1748 return false;
1749
1750 // We only support resuming for byte ranges.
1751 if (request.hasRawHeader("Range")) {
1752 QByteArray range = request.rawHeader("Range");
1753 if (!range.startsWith("bytes="))
1754 return false;
1755 }
1756
1757 // If we're using a download buffer then we don't support resuming/migration
1758 // right now. Too much trouble.
1759 if (downloadZerocopyBuffer)
1760 return false;
1761
1762 return true;
1763}
1764
1765void QNetworkReplyHttpImplPrivate::setResumeOffset(quint64 offset)
1766{
1767 resumeOffset = offset;
1768}
1769
1770void QNetworkReplyHttpImplPrivate::_q_startOperation()
1771{
1772 if (state == Working) // ensure this function is only being called once
1773 return;
1774
1775 state = Working;
1776
1777 postRequest(request);
1778
1779 setupTransferTimeout();
1780 if (synchronous) {
1781 state = Finished;
1782 q_func()->setFinished(true);
1783 }
1784}
1785
1786void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead()
1787{
1788 Q_Q(QNetworkReplyHttpImpl);
1789
1790 if (state != Working)
1791 return;
1792 if (!cacheLoadDevice || !q->isOpen() || !cacheLoadDevice->bytesAvailable())
1793 return;
1794
1795 // FIXME Optimize to use zerocopy download buffer if it is a QBuffer.
1796 // Needs to be done where sendCacheContents() (?) of HTTP is emitting
1797 // metaDataChanged ?
1798
1799
1800 QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
1801
1802 // emit readyRead before downloadProgress incase this will cause events to be
1803 // processed and we get into a recursive call (as in QProgressDialog).
1804
1805 if (!(isHttpRedirectResponse())) {
1806 // This readyRead() goes to the user. The user then may or may not read() anything.
1807 emit q->readyRead();
1808
1809 if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
1810 downloadProgressSignalChoke.restart();
1811 emit q->downloadProgress(bytesDownloaded,
1812 totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
1813 }
1814 }
1815
1816 // A signal we've emitted might be handled by a slot that aborts,
1817 // so we need to check for that and bail out if it's happened:
1818 if (!q->isOpen())
1819 return;
1820
1821 // If there are still bytes available in the cacheLoadDevice then the user did not read
1822 // in response to the readyRead() signal. This means we have to load from the cacheLoadDevice
1823 // and buffer that stuff. This is needed to be able to properly emit finished() later.
1824 while (cacheLoadDevice->bytesAvailable() && !isHttpRedirectResponse())
1825 buffer.append(cacheLoadDevice->readAll());
1826
1827 if (cacheLoadDevice->isSequential()) {
1828 // check if end and we can read the EOF -1
1829 char c;
1830 qint64 actualCount = cacheLoadDevice->read(&c, 1);
1831 if (actualCount < 0) {
1832 cacheLoadDevice->deleteLater();
1833 cacheLoadDevice = nullptr;
1834 QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
1835 } else if (actualCount == 1) {
1836 // This is most probably not happening since most QIODevice returned something proper for bytesAvailable()
1837 // and had already been "emptied".
1838 cacheLoadDevice->ungetChar(c);
1839 }
1840 } else if ((!cacheLoadDevice->isSequential() && cacheLoadDevice->atEnd())) {
1841 // This codepath is in case the cache device is a QBuffer, e.g. from QNetworkDiskCache.
1842 cacheLoadDevice->deleteLater();
1843 cacheLoadDevice = nullptr;
1844 QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
1845 }
1846}
1847
1848
1849void QNetworkReplyHttpImplPrivate::_q_bufferOutgoingDataFinished()
1850{
1851 Q_Q(QNetworkReplyHttpImpl);
1852
1853 // make sure this is only called once, ever.
1854 //_q_bufferOutgoingData may call it or the readChannelFinished emission
1855 if (state != Buffering)
1856 return;
1857
1858 // disconnect signals
1859 QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
1860 QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
1861
1862 // finally, start the request
1863 QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
1864}
1865
1866void QNetworkReplyHttpImplPrivate::_q_cacheSaveDeviceAboutToClose()
1867{
1868 // do not keep a dangling pointer to the device around (device
1869 // is closing because e.g. QAbstractNetworkCache::remove() was called).
1870 cacheSaveDevice = nullptr;
1871}
1872
1873void QNetworkReplyHttpImplPrivate::_q_bufferOutgoingData()
1874{
1875 Q_Q(QNetworkReplyHttpImpl);
1876
1877 if (!outgoingDataBuffer) {
1878 // first call, create our buffer
1879 outgoingDataBuffer = QSharedPointer<QRingBuffer>::create();
1880
1881 QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
1882 QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
1883 }
1884
1885 qint64 bytesBuffered = 0;
1886 qint64 bytesToBuffer = 0;
1887
1888 // read data into our buffer
1889 forever {
1890 bytesToBuffer = outgoingData->bytesAvailable();
1891 // unknown? just try 2 kB, this also ensures we always try to read the EOF
1892 if (bytesToBuffer <= 0)
1893 bytesToBuffer = 2*1024;
1894
1895 char *dst = outgoingDataBuffer->reserve(bytesToBuffer);
1896 bytesBuffered = outgoingData->read(dst, bytesToBuffer);
1897
1898 if (bytesBuffered == -1) {
1899 // EOF has been reached.
1900 outgoingDataBuffer->chop(bytesToBuffer);
1901
1902 _q_bufferOutgoingDataFinished();
1903 break;
1904 } else if (bytesBuffered == 0) {
1905 // nothing read right now, just wait until we get called again
1906 outgoingDataBuffer->chop(bytesToBuffer);
1907
1908 break;
1909 } else {
1910 // don't break, try to read() again
1911 outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered);
1912 }
1913 }
1914}
1915
1916void QNetworkReplyHttpImplPrivate::_q_transferTimedOut()
1917{
1918 Q_Q(QNetworkReplyHttpImpl);
1919 q->abort();
1920}
1921
1922void QNetworkReplyHttpImplPrivate::setupTransferTimeout()
1923{
1924 Q_Q(QNetworkReplyHttpImpl);
1925 if (!transferTimeout) {
1926 transferTimeout = new QTimer(q);
1927 QObject::connect(transferTimeout, SIGNAL(timeout()),
1928 q, SLOT(_q_transferTimedOut()),
1929 Qt::QueuedConnection);
1930 }
1931 transferTimeout->stop();
1932 if (request.transferTimeout()) {
1933 transferTimeout->setSingleShot(true);
1934 transferTimeout->setInterval(request.transferTimeout());
1935 QMetaObject::invokeMethod(transferTimeout, "start",
1936 Qt::QueuedConnection);
1937
1938 }
1939}
1940
1941// need to have this function since the reply is a private member variable
1942// and the special backends need to access this.
1943void QNetworkReplyHttpImplPrivate::emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal)
1944{
1945 Q_Q(QNetworkReplyHttpImpl);
1946 if (isFinished)
1947 return;
1948
1949 setupTransferTimeout();
1950
1951 if (!emitAllUploadProgressSignals) {
1952 //choke signal emissions, except the first and last signals which are unconditional
1953 if (uploadProgressSignalChoke.isValid()) {
1954 if (bytesSent != bytesTotal && uploadProgressSignalChoke.elapsed() < progressSignalInterval) {
1955 return;
1956 }
1957 uploadProgressSignalChoke.restart();
1958 } else {
1959 uploadProgressSignalChoke.start();
1960 }
1961 }
1962 emit q->uploadProgress(bytesSent, bytesTotal);
1963}
1964
1965QNonContiguousByteDevice* QNetworkReplyHttpImplPrivate::createUploadByteDevice()
1966{
1967 Q_Q(QNetworkReplyHttpImpl);
1968
1969 if (outgoingDataBuffer)
1970 uploadByteDevice = QNonContiguousByteDeviceFactory::createShared(outgoingDataBuffer);
1971 else if (outgoingData) {
1972 uploadByteDevice = QNonContiguousByteDeviceFactory::createShared(outgoingData);
1973 } else {
1974 return nullptr;
1975 }
1976
1977 // We want signal emissions only for normal asynchronous uploads
1978 if (!synchronous)
1979 QObject::connect(uploadByteDevice.data(), SIGNAL(readProgress(qint64,qint64)),
1980 q, SLOT(emitReplyUploadProgress(qint64,qint64)));
1981
1982 return uploadByteDevice.data();
1983}
1984
1985void QNetworkReplyHttpImplPrivate::_q_finished()
1986{
1987 // This gets called queued, just forward to real call then
1988 finished();
1989}
1990
1991void QNetworkReplyHttpImplPrivate::finished()
1992{
1993 Q_Q(QNetworkReplyHttpImpl);
1994 if (transferTimeout)
1995 transferTimeout->stop();
1996 if (state == Finished || state == Aborted)
1997 return;
1998
1999 QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
2000
2001 // if we don't know the total size of or we received everything save the cache
2002 if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize)
2003 completeCacheSave();
2004
2005 // We check for errorCode too as in case of SSL handshake failure, we still
2006 // get the HTTP redirect status code (301, 303 etc)
2007 if (isHttpRedirectResponse() && errorCode == QNetworkReply::NoError)
2008 return;
2009
2010 state = Finished;
2011 q->setFinished(true);
2012
2013 if (totalSize.isNull() || totalSize == -1) {
2014 emit q->downloadProgress(bytesDownloaded, bytesDownloaded);
2015 } else {
2016 emit q->downloadProgress(bytesDownloaded, totalSize.toLongLong());
2017 }
2018
2019 if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
2020 emit q->uploadProgress(0, 0);
2021
2022 emit q->readChannelFinished();
2023 emit q->finished();
2024}
2025
2026void QNetworkReplyHttpImplPrivate::_q_error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
2027{
2028 this->error(code, errorMessage);
2029}
2030
2031
2032void QNetworkReplyHttpImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
2033{
2034 Q_Q(QNetworkReplyHttpImpl);
2035 // Can't set and emit multiple errors.
2036 if (errorCode != QNetworkReply::NoError) {
2037 qWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.");
2038 return;
2039 }
2040
2041 errorCode = code;
2042 q->setErrorString(errorMessage);
2043
2044 // note: might not be a good idea, since users could decide to delete us
2045 // which would delete the backend too...
2046 // maybe we should protect the backend
2047 emit q->errorOccurred(code);
2048}
2049
2050void QNetworkReplyHttpImplPrivate::_q_metaDataChanged()
2051{
2052 // FIXME merge this with replyDownloadMetaData(); ?
2053
2054 Q_Q(QNetworkReplyHttpImpl);
2055 // 1. do we have cookies?
2056 // 2. are we allowed to set them?
2057 Q_ASSERT(manager);
2058 const auto it = cookedHeaders.constFind(QNetworkRequest::SetCookieHeader);
2059 if (it != cookedHeaders.cend()
2060 && request.attribute(QNetworkRequest::CookieSaveControlAttribute,
2061 QNetworkRequest::Automatic).toInt() == QNetworkRequest::Automatic) {
2062 QNetworkCookieJar *jar = manager->cookieJar();
2063 if (jar) {
2064 QList<QNetworkCookie> cookies =
2065 qvariant_cast<QList<QNetworkCookie> >(it.value());
2066 jar->setCookiesFromUrl(cookies, url);
2067 }
2068 }
2069 emit q->metaDataChanged();
2070}
2071
2072void QNetworkReplyHttpImplPrivate::createCache()
2073{
2074 // check if we can save and if we're allowed to
2075 if (!managerPrivate->networkCache
2076 || !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool())
2077 return;
2078 cacheEnabled = true;
2079}
2080
2081bool QNetworkReplyHttpImplPrivate::isCachingEnabled() const
2082{
2083 return (cacheEnabled && managerPrivate->networkCache != nullptr);
2084}
2085
2086void QNetworkReplyHttpImplPrivate::setCachingEnabled(bool enable)
2087{
2088 if (!enable && !cacheEnabled)
2089 return; // nothing to do
2090 if (enable && cacheEnabled)
2091 return; // nothing to do either!
2092
2093 if (enable) {
2094 if (Q_UNLIKELY(bytesDownloaded)) {
2095 qDebug() << "setCachingEnabled: " << bytesDownloaded << " bytesDownloaded";
2096 // refuse to enable in this case
2097 qCritical("QNetworkReplyImpl: backend error: caching was enabled after some bytes had been written");
2098 return;
2099 }
2100
2101 createCache();
2102 } else {
2103 // someone told us to turn on, then back off?
2104 // ok... but you should make up your mind
2105 qDebug("QNetworkReplyImpl: setCachingEnabled(true) called after setCachingEnabled(false)");
2106 managerPrivate->networkCache->remove(url);
2107 cacheSaveDevice = nullptr;
2108 cacheEnabled = false;
2109 }
2110}
2111
2112bool QNetworkReplyHttpImplPrivate::isCachingAllowed() const
2113{
2114 return operation == QNetworkAccessManager::GetOperation || operation == QNetworkAccessManager::HeadOperation;
2115}
2116
2117void QNetworkReplyHttpImplPrivate::completeCacheSave()
2118{
2119 if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) {
2120 managerPrivate->networkCache->remove(url);
2121 } else if (cacheEnabled && cacheSaveDevice) {
2122 managerPrivate->networkCache->insert(cacheSaveDevice);
2123 }
2124 cacheSaveDevice = nullptr;
2125 cacheEnabled = false;
2126}
2127
2128QT_END_NAMESPACE
2129