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