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