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

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