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 createUploadByteDevice();
812 break;
813
814 case QNetworkAccessManager::CustomOperation:
815 invalidateCache(); // for safety reasons, we don't know what the operation does
816 httpRequest.setOperation(QHttpNetworkRequest::Custom);
817 createUploadByteDevice();
818 httpRequest.setCustomVerb(newHttpRequest.attribute(
819 QNetworkRequest::CustomVerbAttribute).toByteArray());
820 break;
821
822 default:
823 break; // can't happen
824 }
825
826 QHttpHeaders newRequestHeaders = newHttpRequest.headers();
827 if (resumeOffset != 0) {
828 if (newRequestHeaders.contains(QHttpHeaders::WellKnownHeader::Range)) {
829 // Need to adjust resume offset for user specified range
830
831 // We've already verified that requestRange starts with "bytes=", see canResume.
832 const auto rangeHeader = newRequestHeaders.value(QHttpHeaders::WellKnownHeader::Range);
833 const auto requestRange = QByteArrayView(rangeHeader).mid(bytesEqualPrefix().size());
834
835 newRequestHeaders.removeAll(QHttpHeaders::WellKnownHeader::Range);
836
837 int index = requestRange.indexOf('-');
838
839 quint64 requestStartOffset = requestRange.left(index).toULongLong();
840 quint64 requestEndOffset = requestRange.mid(index + 1).toULongLong();
841
842 // In case an end offset is not given it is skipped from the request range
843 QByteArray newRange = bytesEqualPrefix() + QByteArray::number(resumeOffset + requestStartOffset) +
844 '-' + (requestEndOffset ? QByteArray::number(requestEndOffset) : QByteArray());
845
846 httpRequest.setHeaderField(rangeName(), newRange);
847 } else {
848 httpRequest.setHeaderField(rangeName(), bytesEqualPrefix() + QByteArray::number(resumeOffset) + '-');
849 }
850 }
851
852 for (int i = 0; i < newRequestHeaders.size(); i++) {
853 const auto name = newRequestHeaders.nameAt(i);
854 const auto value = newRequestHeaders.valueAt(i);
855 httpRequest.setHeaderField(QByteArray(name.data(), name.size()), value.toByteArray());
856 }
857
858 if (newHttpRequest.attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool())
859 httpRequest.setPipeliningAllowed(true);
860
861 if (auto allowed = request.attribute(QNetworkRequest::Http2AllowedAttribute);
862 allowed.isValid() && allowed.canConvert<bool>()) {
863 httpRequest.setHTTP2Allowed(allowed.value<bool>());
864 }
865 auto h2cAttribute = request.attribute(QNetworkRequest::Http2CleartextAllowedAttribute);
866 // ### Qt7: Stop checking the environment variable
867 if (h2cAttribute.toBool()
868 || (!h2cAttribute.isValid() && qEnvironmentVariableIsSet("QT_NETWORK_H2C_ALLOWED"))) {
869 httpRequest.setH2cAllowed(true);
870 }
871
872 if (request.attribute(QNetworkRequest::Http2DirectAttribute).toBool()) {
873 // Intentionally mutually exclusive - cannot be both direct and 'allowed'
874 httpRequest.setHTTP2Direct(true);
875 httpRequest.setHTTP2Allowed(false);
876 }
877
878 if (static_cast<QNetworkRequest::LoadControl>
879 (newHttpRequest.attribute(QNetworkRequest::AuthenticationReuseAttribute,
880 QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
881 httpRequest.setWithCredentials(false);
882
883 if (request.attribute(QNetworkRequest::EmitAllUploadProgressSignalsAttribute).toBool())
884 emitAllUploadProgressSignals = true;
885
886 httpRequest.setPeerVerifyName(newHttpRequest.peerVerifyName());
887
888 if (scheme.startsWith(("unix"_L1))) {
889 if (QVariant path = newHttpRequest.attribute(QNetworkRequest::FullLocalServerNameAttribute);
890 path.isValid() && path.canConvert<QString>()) {
891 httpRequest.setFullLocalServerName(path.toString());
892 }
893 }
894
895 // Create the HTTP thread delegate
897 // Propagate Http/2 settings:
898 delegate->http2Parameters = request.http2Configuration();
899 delegate->http1Parameters = request.http1Configuration();
900 delegate->tcpKeepAliveParameters.idleTimeBeforeProbes = request.tcpKeepAliveIdleTimeBeforeProbes();
901 delegate->tcpKeepAliveParameters.intervalBetweenProbes = request.tcpKeepAliveIntervalBetweenProbes();
902 delegate->tcpKeepAliveParameters.probeCount = request.tcpKeepAliveProbeCount();
903
904 if (request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).isValid())
905 delegate->connectionCacheExpiryTimeoutSeconds = request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).toInt();
906
907 // For the synchronous HTTP, this is the normal way the delegate gets deleted
908 // For the asynchronous HTTP this is a safety measure, the delegate deletes itself when HTTP is finished
909 QMetaObject::Connection threadFinishedConnection =
910 QObject::connect(thread, SIGNAL(finished()), delegate, SLOT(deleteLater()));
911
912 // QTBUG-88063: When 'delegate' is deleted the connection will be added to 'thread''s orphaned
913 // connections list. This orphaned list will be cleaned up next time 'thread' emits a signal,
914 // unfortunately that's the finished signal. It leads to a soft-leak so we do this to disconnect
915 // it on deletion so that it cleans up the orphan immediately.
916 QObject::connect(delegate, &QObject::destroyed,
917 delegate, [threadFinishedConnection] () mutable {
918 if (bool(threadFinishedConnection))
919 QObject::disconnect(threadFinishedConnection);
920 });
921
922 // Set the properties it needs
923 delegate->httpRequest = httpRequest;
924#ifndef QT_NO_NETWORKPROXY
925 delegate->cacheProxy = cacheProxy;
926 delegate->transparentProxy = transparentProxy;
927#endif
928 delegate->ssl = ssl;
929#ifndef QT_NO_SSL
930 if (ssl)
931 delegate->incomingSslConfiguration.reset(new QSslConfiguration(newHttpRequest.sslConfiguration()));
932#endif
933
934 // Do we use synchronous HTTP?
935 delegate->synchronous = synchronous;
936
937 // The authentication manager is used to avoid the BlockingQueuedConnection communication
938 // from HTTP thread to user thread in some cases.
939 delegate->authenticationManager = managerPrivate->authenticationManager;
940
941 if (!synchronous) {
942 // Tell our zerocopy policy to the delegate
943 QVariant downloadBufferMaximumSizeAttribute = newHttpRequest.attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute);
944 if (downloadBufferMaximumSizeAttribute.isValid()) {
945 delegate->downloadBufferMaximumSize = downloadBufferMaximumSizeAttribute.toLongLong();
946 } else {
947 // If there is no MaximumDownloadBufferSizeAttribute set (which is for the majority
948 // of QNetworkRequest) then we can assume we'll do it anyway for small HTTP replies.
949 // This helps with performance and memory fragmentation.
950 delegate->downloadBufferMaximumSize = 128*1024;
951 }
952
953
954 // These atomic integers are used for signal compression
955 delegate->pendingDownloadData = pendingDownloadDataEmissions;
956 delegate->pendingDownloadProgress = pendingDownloadProgressEmissions;
957
958 // Connect the signals of the delegate to us
959 QObject::connect(delegate, SIGNAL(downloadData(QByteArray)),
960 q, SLOT(replyDownloadData(QByteArray)),
961 Qt::QueuedConnection);
962 QObject::connect(delegate, SIGNAL(downloadFinished()),
963 q, SLOT(replyFinished()),
964 Qt::QueuedConnection);
965 QObject::connect(delegate, &QHttpThreadDelegate::socketStartedConnecting,
966 q, &QNetworkReply::socketStartedConnecting, Qt::QueuedConnection);
967 QObject::connect(delegate, &QHttpThreadDelegate::requestSent,
968 q, &QNetworkReply::requestSent, Qt::QueuedConnection);
969 connect(delegate, &QHttpThreadDelegate::downloadMetaData, this,
970 &QNetworkReplyHttpImplPrivate::replyDownloadMetaData, Qt::QueuedConnection);
971 QObject::connect(delegate, SIGNAL(downloadProgress(qint64,qint64)),
972 q, SLOT(replyDownloadProgressSlot(qint64,qint64)),
973 Qt::QueuedConnection);
974 QObject::connect(delegate, SIGNAL(error(QNetworkReply::NetworkError,QString)),
975 q, SLOT(httpError(QNetworkReply::NetworkError,QString)),
976 Qt::QueuedConnection);
977 QObject::connect(delegate, SIGNAL(redirected(QUrl,int,int)),
978 q, SLOT(onRedirected(QUrl,int,int)),
979 Qt::QueuedConnection);
980
981#ifndef QT_NO_SSL
982 QObject::connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)),
983 q, SLOT(replySslConfigurationChanged(QSslConfiguration)),
984 Qt::QueuedConnection);
985#endif
986 // Those need to report back, therefore BlockingQueuedConnection
987 QObject::connect(delegate, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
988 q, SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
989 Qt::BlockingQueuedConnection);
990#ifndef QT_NO_NETWORKPROXY
991 QObject::connect(delegate, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
992 q, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
993 Qt::BlockingQueuedConnection);
994#endif
995#ifndef QT_NO_SSL
996 QObject::connect(delegate, SIGNAL(encrypted()), q, SLOT(replyEncrypted()),
997 Qt::BlockingQueuedConnection);
998 QObject::connect(delegate, SIGNAL(sslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
999 q, SLOT(replySslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
1000 Qt::BlockingQueuedConnection);
1001 QObject::connect(delegate, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
1002 q, SLOT(replyPreSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator*)),
1003 Qt::BlockingQueuedConnection);
1004#endif
1005 // This signal we will use to start the request.
1006 QObject::connect(q, SIGNAL(startHttpRequest()), delegate, SLOT(startRequest()));
1007 QObject::connect(q, SIGNAL(abortHttpRequest()), delegate, SLOT(abortRequest()));
1008
1009 // To throttle the connection.
1010 QObject::connect(q, SIGNAL(readBufferSizeChanged(qint64)), delegate, SLOT(readBufferSizeChanged(qint64)));
1011 QObject::connect(q, SIGNAL(readBufferFreed(qint64)), delegate, SLOT(readBufferFreed(qint64)));
1012
1013 if (uploadByteDevice) {
1014 QNonContiguousByteDeviceThreadForwardImpl *forwardUploadDevice =
1015 new QNonContiguousByteDeviceThreadForwardImpl(uploadByteDevice->atEnd(), uploadByteDevice->size());
1016 forwardUploadDevice->setParent(delegate); // needed to make sure it is moved on moveToThread()
1017 delegate->httpRequest.setUploadByteDevice(forwardUploadDevice);
1018
1019 // If the device in the user thread claims it has more data, keep the flow to HTTP thread going
1020 QObject::connect(uploadByteDevice.get(), SIGNAL(readyRead()),
1021 q, SLOT(uploadByteDeviceReadyReadSlot()),
1022 Qt::QueuedConnection);
1023
1024 // From user thread to http thread:
1025 QObject::connect(q, SIGNAL(haveUploadData(qint64,QByteArray,bool,qint64)),
1026 forwardUploadDevice, SLOT(haveDataSlot(qint64,QByteArray,bool,qint64)), Qt::QueuedConnection);
1027 QObject::connect(uploadByteDevice.get(), SIGNAL(readyRead()),
1028 forwardUploadDevice, SIGNAL(readyRead()),
1029 Qt::QueuedConnection);
1030
1031 // From http thread to user thread:
1032 QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)),
1033 q, SLOT(wantUploadDataSlot(qint64)));
1034 QObject::connect(forwardUploadDevice,SIGNAL(processedData(qint64,qint64)),
1035 q, SLOT(sentUploadDataSlot(qint64,qint64)));
1036 QObject::connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
1037 q, SLOT(resetUploadDataSlot(bool*)),
1038 Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
1039 }
1040 } else if (synchronous) {
1041 QObject::connect(q, SIGNAL(startHttpRequestSynchronously()), delegate, SLOT(startRequestSynchronously()), Qt::BlockingQueuedConnection);
1042
1043 if (uploadByteDevice) {
1044 // For the synchronous HTTP use case the use thread (this one here) is blocked
1045 // so we cannot use the asynchronous upload architecture.
1046 // We therefore won't use the QNonContiguousByteDeviceThreadForwardImpl but directly
1047 // use the uploadByteDevice provided to us by the QNetworkReplyImpl.
1048 // The code that is in start() makes sure it is safe to use from a thread
1049 // since it only wraps a QRingBuffer
1050 delegate->httpRequest.setUploadByteDevice(uploadByteDevice.get());
1051 }
1052 }
1053
1054
1055 // Move the delegate to the http thread
1056 delegate->moveToThread(thread);
1057 // This call automatically moves the uploadDevice too for the asynchronous case.
1058
1059 // Prepare timers for progress notifications
1060 downloadProgressSignalChoke.start();
1061 uploadProgressSignalChoke.invalidate();
1062
1063 // Send an signal to the delegate so it starts working in the other thread
1064 if (synchronous) {
1065 emit q->startHttpRequestSynchronously(); // This one is BlockingQueuedConnection, so it will return when all work is done
1066
1067 replyDownloadMetaData
1068 (delegate->incomingHeaders,
1069 delegate->incomingStatusCode,
1070 delegate->incomingReasonPhrase,
1071 delegate->isPipeliningUsed,
1072 QSharedPointer<char>(),
1073 delegate->incomingContentLength,
1074 delegate->removedContentLength,
1075 delegate->isHttp2Used,
1076 delegate->isCompressed);
1077 replyDownloadData(delegate->synchronousDownloadData);
1078
1079 if (delegate->incomingErrorCode != QNetworkReply::NoError)
1080 httpError(delegate->incomingErrorCode, delegate->incomingErrorDetail);
1081
1082 thread->quit();
1083 thread->wait(QDeadlineTimer(5000));
1084 if (thread->isFinished())
1085 delete thread;
1086 else
1087 QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
1088
1089 finished();
1090 } else {
1091 emit q->startHttpRequest(); // Signal to the HTTP thread and go back to user.
1092 }
1093}
1094
1096{
1097 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1098 if (nc)
1099 nc->remove(httpRequest.url());
1100}
1101
1103{
1104 Q_Q(QNetworkReplyHttpImpl);
1105
1106 // The disk cache does not support partial content, so don't even try to
1107 // save any such content into the cache.
1108 if (q->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 206) {
1109 cacheEnabled = false;
1110 return;
1111 }
1112
1113 // save the meta data
1114 QNetworkCacheMetaData metaData;
1115 metaData.setUrl(url);
1116 metaData = fetchCacheMetaData(metaData);
1117
1118 // save the redirect request also in the cache
1119 QVariant redirectionTarget = q->attribute(QNetworkRequest::RedirectionTargetAttribute);
1120 if (redirectionTarget.isValid()) {
1121 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
1122 attributes.insert(QNetworkRequest::RedirectionTargetAttribute, redirectionTarget);
1123 metaData.setAttributes(attributes);
1124 }
1125
1126 cacheSaveDevice = managerPrivate->networkCache->prepare(metaData);
1127
1128 if (cacheSaveDevice)
1129 q->connect(cacheSaveDevice, SIGNAL(aboutToClose()), SLOT(_q_cacheSaveDeviceAboutToClose()));
1130
1131 if (!cacheSaveDevice || !cacheSaveDevice->isOpen()) {
1132 if (Q_UNLIKELY(cacheSaveDevice && !cacheSaveDevice->isOpen()))
1133 qCritical("QNetworkReplyImpl: network cache returned a device that is not open -- "
1134 "class %s probably needs to be fixed",
1135 managerPrivate->networkCache->metaObject()->className());
1136
1137 managerPrivate->networkCache->remove(url);
1138 cacheSaveDevice = nullptr;
1139 cacheEnabled = false;
1140 }
1141}
1142
1144{
1145 Q_Q(QNetworkReplyHttpImpl);
1146
1147 // If we're closed just ignore this data
1148 if (!q->isOpen())
1149 return;
1150
1151 // cache this, we need it later and it's invalidated when dealing with compressed data
1152 auto dataSize = d.size();
1153
1156
1157 if (decompressHelper.isValid()) {
1158 qint64 uncompressedBefore = -1;
1159 if (decompressHelper.isCountingBytes())
1160 uncompressedBefore = decompressHelper.uncompressedSize();
1161
1162 decompressHelper.feed(std::move(d));
1163
1164 if (!decompressHelper.isValid()) {
1165 error(QNetworkReplyImpl::NetworkError::UnknownContentError,
1166 QCoreApplication::translate("QHttp", "Decompression failed: %1")
1167 .arg(decompressHelper.errorString()));
1168 decompressHelper.clear();
1169 return;
1170 }
1171
1173 if (decompressHelper.isCountingBytes())
1174 bytesDownloaded += (decompressHelper.uncompressedSize() - uncompressedBefore);
1176 }
1177
1178 if (synchronous) {
1179 d = QByteArray();
1180 const qsizetype increments = 16 * 1024;
1181 qint64 bytesRead = 0;
1182 while (decompressHelper.hasData()) {
1183 quint64 nextSize = quint64(d.size()) + quint64(increments);
1184 if (nextSize > quint64(std::numeric_limits<QByteArray::size_type>::max())) {
1185 error(QNetworkReplyImpl::NetworkError::UnknownContentError,
1186 QCoreApplication::translate("QHttp",
1187 "Data downloaded is too large to store"));
1188 decompressHelper.clear();
1189 return;
1190 }
1191 d.resize(nextSize);
1192 bytesRead += decompressHelper.read(d.data() + bytesRead, increments);
1193 if (!decompressHelper.isValid()) {
1194 error(QNetworkReplyImpl::NetworkError::UnknownContentError,
1195 QCoreApplication::translate("QHttp", "Decompression failed: %1")
1196 .arg(decompressHelper.errorString()));
1197 decompressHelper.clear();
1198 return;
1199 }
1200 }
1201 d.resize(bytesRead);
1202 // we're synchronous so we're not calling this function again; reset the decompressHelper
1203 decompressHelper.clear();
1204 }
1205 }
1206
1207 // This is going to look a little strange. When downloading data while a
1208 // HTTP redirect is happening (and enabled), we write the redirect
1209 // response to the cache. However, we do not append it to our internal
1210 // buffer as that will contain the response data only for the final
1211 // response
1212 // Note: For compressed data this is done in readData()
1213 if (cacheSaveDevice && !decompressHelper.isValid()) {
1214 cacheSaveDevice->write(d);
1215 }
1216
1217 // if decompressHelper is valid then we have compressed data, and this is handled above
1218 if (!decompressHelper.isValid() && !isHttpRedirectResponse()) {
1219 buffer.append(d);
1220 bytesDownloaded += dataSize;
1222 }
1223 bytesBuffered += dataSize;
1224
1225 int pendingSignals = pendingDownloadDataEmissions->fetchAndSubAcquire(1) - 1;
1226 if (pendingSignals > 0) {
1227 // Some more signal emissions to this slot are pending.
1228 // Instead of writing the downstream data, we wait
1229 // and do it in the next call we get
1230 // (signal comppression)
1231 return;
1232 }
1233
1235 return;
1236
1237 // This can occur when downloading compressed data as some of the data may be the content
1238 // encoding's header. Don't emit anything for this.
1239 if (lastReadyReadEmittedSize == bytesDownloaded) {
1240 if (readBufferMaxSize)
1241 emit q->readBufferFreed(dataSize);
1242 return;
1243 }
1244 lastReadyReadEmittedSize = bytesDownloaded;
1245
1246 const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
1247 headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
1248
1249 emit q->readyRead();
1250 // emit readyRead before downloadProgress in case this will cause events to be
1251 // processed and we get into a recursive call (as in QProgressDialog).
1252 if (downloadProgressSignalChoke.isValid() &&
1253 downloadProgressSignalChoke.elapsed() >= progressSignalInterval
1254 && (!decompressHelper.isValid() || decompressHelper.isCountingBytes())) {
1255 downloadProgressSignalChoke.start();
1256 emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1));
1257 }
1258}
1259
1261{
1262 // We are already loading from cache, we still however
1263 // got this signal because it was posted already
1264 if (loadingFromCache)
1265 return;
1266
1267 finished();
1268}
1269
1270QNetworkAccessManager::Operation QNetworkReplyHttpImplPrivate::getRedirectOperation(QNetworkAccessManager::Operation currentOp, int httpStatus)
1271{
1272 // HTTP status code can be used to decide if we can redirect with a GET
1273 // operation or not. See http://www.ietf.org/rfc/rfc2616.txt [Sec 10.3] for
1274 // more details
1275
1276 // We MUST keep using the verb that was used originally when being redirected with 307 or 308.
1277 if (httpStatus == 307 || httpStatus == 308)
1278 return currentOp;
1279
1280 switch (currentOp) {
1281 case QNetworkAccessManager::HeadOperation:
1282 return QNetworkAccessManager::HeadOperation;
1283 default:
1284 break;
1285 }
1286 // Use GET for everything else.
1287 return QNetworkAccessManager::GetOperation;
1288}
1289
1291{
1292 return httpRequest.isFollowRedirects() && QHttpNetworkReply::isHttpRedirect(statusCode);
1293}
1294
1295QNetworkRequest QNetworkReplyHttpImplPrivate::createRedirectRequest(const QNetworkRequest &originalRequest,
1296 const QUrl &url,
1297 int maxRedirectsRemaining)
1298{
1299 QNetworkRequest newRequest(originalRequest);
1300 newRequest.setUrl(url);
1301 newRequest.setMaximumRedirectsAllowed(maxRedirectsRemaining);
1302
1303 return newRequest;
1304}
1305
1306void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int httpStatus, int maxRedirectsRemaining)
1307{
1308 Q_Q(QNetworkReplyHttpImpl);
1309 Q_ASSERT(manager);
1310 Q_ASSERT(managerPrivate);
1311
1312 if (isFinished)
1313 return;
1314
1315 const QString schemeBefore(url.scheme());
1316 if (httpRequest.isFollowRedirects()) // update the reply's url as it could've changed
1317 url = redirectUrl;
1318
1319 const bool wasLocalSocket = schemeBefore.startsWith("unix"_L1);
1320 if (!wasLocalSocket && managerPrivate->stsEnabled && managerPrivate->stsCache.isKnownHost(url)) {
1321 // RFC6797, 8.3:
1322 // The UA MUST replace the URI scheme with "https" [RFC2818],
1323 // and if the URI contains an explicit port component of "80",
1324 // then the UA MUST convert the port component to be "443", or
1325 // if the URI contains an explicit port component that is not
1326 // equal to "80", the port component value MUST be preserved;
1327 // otherwise, if the URI does not contain an explicit port
1328 // component, the UA MUST NOT add one.
1329 url.setScheme("https"_L1);
1330 if (url.port() == 80)
1331 url.setPort(443);
1332 }
1333
1334 // Just to be on the safe side for local sockets, any changes to the scheme
1335 // are considered less safe
1336 const bool changingLocalScheme = wasLocalSocket && url.scheme() != schemeBefore;
1337 const bool isLessSafe = changingLocalScheme
1338 || (schemeBefore == "https"_L1 && url.scheme() == "http"_L1);
1339 if (httpRequest.redirectPolicy() == QNetworkRequest::NoLessSafeRedirectPolicy && isLessSafe) {
1340 error(QNetworkReply::InsecureRedirectError,
1341 QCoreApplication::translate("QHttp", "Insecure redirect"));
1342 return;
1343 }
1344
1345 // If the original operation was a GET with a body and the status code is
1346 // 308 then keep the message body. The body must still be present though:
1347 // a previous redirect (e.g. 301/302/303) has already dropped it, and once
1348 // dropped it cannot be restored, so the headers must be dropped too to keep
1349 // them in sync (QTBUG-133976).
1350 const bool getOperationKeepsBody = (operation == QNetworkAccessManager::GetOperation)
1351 && httpStatus == 308
1352 && (outgoingData || outgoingDataBuffer);
1353
1354 redirectRequest = createRedirectRequest(originalRequest, url, maxRedirectsRemaining);
1355 operation = getRedirectOperation(operation, httpStatus);
1356
1357 // Clear stale headers, the relevant ones get set again later
1358 httpRequest.clearHeaders();
1359 auto newHeaders = redirectRequest.headers();
1360 if ((operation == QNetworkAccessManager::GetOperation
1361 || operation == QNetworkAccessManager::HeadOperation) && !getOperationKeepsBody) {
1362 // possibly changed from not-GET/HEAD to GET/HEAD, make sure to get rid of upload device
1363 uploadByteDevice.reset();
1364 uploadByteDevicePosition = 0;
1365 if (outgoingData) {
1366 QObject::disconnect(outgoingData, SIGNAL(readyRead()), q,
1367 SLOT(_q_bufferOutgoingData()));
1368 QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q,
1369 SLOT(_q_bufferOutgoingDataFinished()));
1370 }
1371 outgoingData = nullptr;
1372 outgoingDataBuffer.reset();
1373 // We need to explicitly unset these headers so they're not reapplied to the httpRequest
1374 newHeaders.removeAll(QHttpHeaders::WellKnownHeader::ContentLength);
1375 newHeaders.removeAll(QHttpHeaders::WellKnownHeader::ContentType);
1376 }
1377
1378 if (const QNetworkCookieJar *const cookieJar = manager->cookieJar()) {
1379 auto cookies = cookieJar->cookiesForUrl(url);
1380 if (!cookies.empty()) {
1381 auto cookieHeader = QNetworkHeadersPrivate::fromCookieList(cookies);
1382 newHeaders.replaceOrAppend(QHttpHeaders::WellKnownHeader::Cookie, cookieHeader);
1383 }
1384 }
1385
1386 redirectRequest.setHeaders(std::move(newHeaders));
1387
1388 if (httpRequest.redirectPolicy() != QNetworkRequest::UserVerifiedRedirectPolicy)
1390
1391 emit q->redirected(url);
1392}
1393
1395{
1396 Q_Q(QNetworkReplyHttpImpl);
1397 Q_ASSERT(managerPrivate);
1398
1399 decompressHelper.clear();
1400 clearHeaders();
1401
1402 if (managerPrivate->thread)
1403 managerPrivate->thread->disconnect();
1404
1405 QMetaObject::invokeMethod(
1406 q,
1407 [this] {
1408 postRequest(redirectRequest);
1409 setupTransferTimeout();
1410 },
1411 Qt::QueuedConnection);
1412}
1413
1414static constexpr QLatin1StringView locationHeader() noexcept { return "location"_L1; }
1415
1417{
1418 Q_Q(QNetworkReplyHttpImpl);
1419 switch (statusCode) {
1420 case 301: // Moved Permanently
1421 case 302: // Found
1422 case 303: // See Other
1423 case 307: // Temporary Redirect
1424 case 308: // Permanent Redirect
1425 // What do we do about the caching of the HTML note?
1426 // The response to a 303 MUST NOT be cached, while the response to
1427 // all of the others is cacheable if the headers indicate it to be
1428 QByteArrayView header = q->headers().value(locationHeader());
1429 QUrl url = QUrl(QString::fromUtf8(header));
1430 if (!url.isValid())
1431 url = QUrl(QLatin1StringView(header));
1432 q->setAttribute(QNetworkRequest::RedirectionTargetAttribute, url);
1433 }
1434}
1435
1436void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QHttpHeaders &hm,
1437 int sc, const QString &rp, bool pu,
1438 QSharedPointer<char> db,
1439 qint64 contentLength,
1440 qint64 removedContentLength,
1441 bool h2Used, bool isCompressed)
1442{
1443 Q_Q(QNetworkReplyHttpImpl);
1444 Q_UNUSED(contentLength);
1445
1446 statusCode = sc;
1447 reasonPhrase = rp;
1448
1449#ifndef QT_NO_SSL
1450 // We parse this header only if we're using secure transport:
1451 //
1452 // RFC6797, 8.1
1453 // If an HTTP response is received over insecure transport, the UA MUST
1454 // ignore any present STS header field(s).
1455 if (url.scheme() == "https"_L1 && managerPrivate->stsEnabled)
1456 managerPrivate->stsCache.updateFromHeaders(hm, url);
1457#endif
1458 // Download buffer
1459 if (!db.isNull()) {
1460 downloadBufferPointer = db;
1461 downloadZerocopyBuffer = downloadBufferPointer.data();
1462 downloadBufferCurrentSize = 0;
1463 q->setAttribute(QNetworkRequest::DownloadBufferAttribute, QVariant::fromValue<QSharedPointer<char> > (downloadBufferPointer));
1464 }
1465
1466 q->setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, pu);
1467 q->setAttribute(QNetworkRequest::Http2WasUsedAttribute, h2Used);
1468
1469 // A user having manually defined which encodings they accept is, for
1470 // somwehat unknown (presumed legacy compatibility) reasons treated as
1471 // disabling our decompression:
1472 const bool autoDecompress = !request.headers().contains(QHttpHeaders::WellKnownHeader::AcceptEncoding);
1473 const bool shouldDecompress = isCompressed && autoDecompress;
1474 // reconstruct the HTTP header
1475 auto h = q->headers();
1476 for (qsizetype i = 0; i < hm.size(); ++i) {
1477 const auto key = hm.nameAt(i);
1478 const auto originValue = hm.valueAt(i);
1479
1480 // Reset any previous "location" header set in the reply. In case of
1481 // redirects, we don't want to 'append' multiple location header values,
1482 // rather we keep only the latest one
1483 if (key.compare(locationHeader(), Qt::CaseInsensitive) == 0)
1484 h.removeAll(key);
1485
1486 if (shouldDecompress && !decompressHelper.isValid()
1487 && key == QHttpHeaders::wellKnownHeaderName(QHttpHeaders::WellKnownHeader::ContentEncoding)) {
1488 if (!synchronous) // with synchronous all the data is expected to be handled at once
1489 decompressHelper.setCountingBytesEnabled(true);
1490
1491 if (!decompressHelper.setEncoding(originValue)) {
1492 error(QNetworkReplyImpl::NetworkError::UnknownContentError,
1493 QCoreApplication::translate("QHttp", "Failed to initialize decompression: %1")
1494 .arg(decompressHelper.errorString()));
1495 return;
1496 }
1497 decompressHelper.setDecompressedSafetyCheckThreshold(
1498 request.decompressedSafetyCheckThreshold());
1499 }
1500
1501 h.append(key, originValue);
1502 }
1503 q->setHeaders(std::move(h));
1504
1505 q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1506 q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
1507 if (removedContentLength != -1)
1508 q->setAttribute(QNetworkRequest::OriginalContentLengthAttribute, removedContentLength);
1509
1510 // is it a redirection?
1513
1514 if (statusCode >= 500 && statusCode < 600) {
1515 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1516 if (nc) {
1517 QNetworkCacheMetaData metaData = nc->metaData(httpRequest.url());
1518 auto value = metaData.headers().value(QHttpHeaders::WellKnownHeader::CacheControl);
1519 bool mustReValidate = false;
1520 if (!value.empty()) {
1521 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(value);
1522 if (cacheControl.contains("must-revalidate"_ba))
1523 mustReValidate = true;
1524 }
1525 if (!mustReValidate && sendCacheContents(metaData))
1526 return;
1527 }
1528 }
1529
1530 if (statusCode == 304) {
1531#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1532 qDebug() << "Received a 304 from" << request.url();
1533#endif
1534 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1535 if (nc) {
1536 QNetworkCacheMetaData oldMetaData = nc->metaData(httpRequest.url());
1537 QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData);
1538 if (oldMetaData != metaData)
1539 nc->updateMetaData(metaData);
1540 if (sendCacheContents(metaData))
1541 return;
1542 }
1543 }
1544
1545
1546 if (statusCode != 304 && statusCode != 303) {
1547 if (!isCachingEnabled())
1549 }
1550
1552}
1553
1554void QNetworkReplyHttpImplPrivate::replyDownloadProgressSlot(qint64 bytesReceived, qint64 bytesTotal)
1555{
1556 Q_Q(QNetworkReplyHttpImpl);
1557
1558 // If we're closed just ignore this data
1559 if (!q->isOpen())
1560 return;
1561
1562 // we can be sure here that there is a download buffer
1563
1564 int pendingSignals = (int)pendingDownloadProgressEmissions->fetchAndAddAcquire(-1) - 1;
1565 if (pendingSignals > 0) {
1566 // Let's ignore this signal and look at the next one coming in
1567 // (signal comppression)
1568 return;
1569 }
1570
1571 if (!q->isOpen())
1572 return;
1573
1574 if (cacheEnabled && isCachingAllowed() && bytesReceived == bytesTotal) {
1575 // Write everything in one go if we use a download buffer. might be more performant.
1577 // need to check again if cache enabled and device exists
1579 cacheSaveDevice->write(downloadZerocopyBuffer, bytesTotal);
1580 // FIXME where is it closed?
1581 }
1582
1584 return;
1585
1586 bytesDownloaded = bytesReceived;
1588
1589 downloadBufferCurrentSize = bytesReceived;
1590
1591 // Only emit readyRead when actual data is there
1592 // emit readyRead before downloadProgress in case this will cause events to be
1593 // processed and we get into a recursive call (as in QProgressDialog).
1594 if (bytesDownloaded > 0)
1595 emit q->readyRead();
1596 if (downloadProgressSignalChoke.isValid() &&
1597 downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
1598 downloadProgressSignalChoke.start();
1599 emit q->downloadProgress(bytesDownloaded, bytesTotal);
1600 }
1601}
1602
1603void QNetworkReplyHttpImplPrivate::httpAuthenticationRequired(const QHttpNetworkRequest &request,
1604 QAuthenticator *auth)
1605{
1606 managerPrivate->authenticationRequired(auth, q_func(), synchronous, url, &urlForLastAuthentication, request.withCredentials());
1607}
1608
1609#ifndef QT_NO_NETWORKPROXY
1610void QNetworkReplyHttpImplPrivate::proxyAuthenticationRequired(const QNetworkProxy &proxy,
1611 QAuthenticator *authenticator)
1612{
1613 managerPrivate->proxyAuthenticationRequired(request.url(), proxy, synchronous, authenticator, &lastProxyAuthentication);
1614}
1615#endif
1616
1617void QNetworkReplyHttpImplPrivate::httpError(QNetworkReply::NetworkError errorCode,
1618 const QString &errorString)
1619{
1620#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1621 qDebug() << "http error!" << errorCode << errorString;
1622#endif
1623
1624 // FIXME?
1625 error(errorCode, errorString);
1626}
1627
1628#ifndef QT_NO_SSL
1630{
1631 Q_Q(QNetworkReplyHttpImpl);
1632 emit q->encrypted();
1633}
1634
1636 const QList<QSslError> &list, bool *ignoreAll, QList<QSslError> *toBeIgnored)
1637{
1638 Q_Q(QNetworkReplyHttpImpl);
1639 emit q->sslErrors(list);
1640 // Check if the callback set any ignore and return this here to http thread
1642 *ignoreAll = true;
1643 if (!pendingIgnoreSslErrorsList.isEmpty())
1644 *toBeIgnored = pendingIgnoreSslErrorsList;
1645}
1646
1647void QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfiguration &newSslConfiguration)
1648{
1649 // Receiving the used SSL configuration from the HTTP thread
1650 if (sslConfiguration)
1651 *sslConfiguration = newSslConfiguration;
1652 else
1653 sslConfiguration.reset(new QSslConfiguration(newSslConfiguration));
1654}
1655
1657{
1658 Q_Q(QNetworkReplyHttpImpl);
1659 emit q->preSharedKeyAuthenticationRequired(authenticator);
1660}
1661#endif
1662
1663// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1665{
1666 *r = uploadByteDevice->reset();
1667 if (*r) {
1668 // reset our own position which is used for the inter-thread communication
1669 uploadByteDevicePosition = 0;
1670 }
1671}
1672
1673// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1674void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 pos, qint64 amount)
1675{
1676 if (!uploadByteDevice) // uploadByteDevice is no longer available
1677 return;
1678
1679 if (uploadByteDevicePosition + amount != pos) {
1680 // Sanity check, should not happen.
1681 error(QNetworkReply::UnknownNetworkError, QString());
1682 return;
1683 }
1684 uploadByteDevice->advanceReadPointer(amount);
1685 uploadByteDevicePosition += amount;
1686}
1687
1688// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1690{
1691 Q_Q(QNetworkReplyHttpImpl);
1692
1693 if (!uploadByteDevice) // uploadByteDevice is no longer available
1694 return;
1695
1696 // call readPointer
1697 qint64 currentUploadDataLength = 0;
1698 char *data = const_cast<char*>(uploadByteDevice->readPointer(maxSize, currentUploadDataLength));
1699
1700 if (currentUploadDataLength == 0) {
1701 uploadDeviceChoking = true;
1702 // No bytes from upload byte device. There will be bytes later, it will emit readyRead()
1703 // and our uploadByteDeviceReadyReadSlot() is called.
1704 return;
1705 } else {
1706 uploadDeviceChoking = false;
1707 }
1708
1709 // Let's make a copy of this data
1710 QByteArray dataArray(data, currentUploadDataLength);
1711
1712 // Communicate back to HTTP thread
1713 emit q->haveUploadData(uploadByteDevicePosition, dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
1714}
1715
1717{
1718 // Start the flow between this thread and the HTTP thread again by triggering a upload.
1719 // However only do this when we were choking before, else the state in
1720 // QNonContiguousByteDeviceThreadForwardImpl gets messed up.
1721 if (uploadDeviceChoking) {
1722 uploadDeviceChoking = false;
1723 wantUploadDataSlot(1024);
1724 }
1725}
1726
1727
1728/*
1729 A simple web page that can be used to test us: http://www.procata.com/cachetest/
1730 */
1731bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData &metaData)
1732{
1733 Q_Q(QNetworkReplyHttpImpl);
1734
1736 if (!metaData.isValid())
1737 return false;
1738
1739 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1740 Q_ASSERT(nc);
1741 QIODevice *contents = nc->data(url);
1742 if (!contents) {
1743#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1744 qDebug() << "Cannot send cache, the contents are 0" << url;
1745#endif
1746 return false;
1747 }
1748 contents->setParent(q);
1749
1750 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
1751 int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt();
1752 if (status < 100)
1753 status = 200; // fake it
1754
1755 statusCode = status;
1756
1757 q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
1758 q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
1759 q->setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
1760
1761 QHttpHeaders cachedHeaders = metaData.headers();
1762 QHttpHeaders h = headers();
1763 QUrl redirectUrl;
1764 for (qsizetype i = 0; i < cachedHeaders.size(); ++i) {
1765 const auto name = cachedHeaders.nameAt(i);
1766 const auto value = cachedHeaders.valueAt(i);
1767
1768 if (httpRequest.isFollowRedirects()
1769 && !name.compare(locationHeader(), Qt::CaseInsensitive)) {
1770 redirectUrl = QUrl::fromEncoded(value);
1771 }
1772
1773 h.replaceOrAppend(name, value);
1774 }
1775 setHeaders(std::move(h));
1776
1778 checkForRedirect(status);
1779
1780 cacheLoadDevice = contents;
1781 q->connect(cacheLoadDevice, SIGNAL(readyRead()), SLOT(_q_cacheLoadReadyRead()));
1782 q->connect(cacheLoadDevice, SIGNAL(readChannelFinished()), SLOT(_q_cacheLoadReadyRead()));
1783
1784 // This needs to be emitted in the event loop because it can be reached at
1785 // the direct code path of qnam.get(...) before the user has a chance
1786 // to connect any signals.
1787 QMetaObject::invokeMethod(q, "_q_metaDataChanged", Qt::QueuedConnection);
1788 QMetaObject::invokeMethod(q, "_q_cacheLoadReadyRead", Qt::QueuedConnection);
1789
1790
1791#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1792 qDebug() << "Successfully sent cache:" << url << contents->size() << "bytes";
1793#endif
1794
1795 // Do redirect processing
1796 if (httpRequest.isFollowRedirects() && QHttpNetworkReply::isHttpRedirect(status)) {
1797 QMetaObject::invokeMethod(q, "onRedirected", Qt::QueuedConnection,
1798 Q_ARG(QUrl, redirectUrl),
1799 Q_ARG(int, status),
1800 Q_ARG(int, httpRequest.redirectCount() - 1));
1801 }
1802
1803 // Set the following flag so we can ignore some signals from HTTP thread
1804 // that would still come
1805 loadingFromCache = true;
1806 return true;
1807}
1808
1809static auto caseInsensitiveCompare(QByteArrayView value)
1810{
1811 return [value](QByteArrayView element)
1812 {
1813 return value.compare(element, Qt::CaseInsensitive) == 0;
1814 };
1815}
1816
1817static bool isHopByHop(QByteArrayView header)
1818{
1819 constexpr QByteArrayView headers[] = { "connection",
1820 "keep-alive",
1821 "proxy-authenticate",
1822 "proxy-authorization",
1823 "te",
1824 "trailers",
1825 "transfer-encoding",
1826 "upgrade"};
1827 return std::any_of(std::begin(headers), std::end(headers), caseInsensitiveCompare(header));
1828}
1829
1830QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const
1831{
1832 Q_Q(const QNetworkReplyHttpImpl);
1833
1834 QNetworkCacheMetaData metaData = oldMetaData;
1835 QHttpHeaders cacheHeaders = metaData.headers();
1836
1837 const auto newHeaders = q->headers();
1838 for (qsizetype i = 0; i < newHeaders.size(); ++i) {
1839 const auto name = newHeaders.nameAt(i);
1840 const auto value = newHeaders.valueAt(i);
1841
1842 if (isHopByHop(name))
1843 continue;
1844
1845 if (name.compare("set-cookie", Qt::CaseInsensitive) == 0)
1846 continue;
1847
1848 // for 4.6.0, we were planning to not store the date header in the
1849 // cached resource; through that we planned to reduce the number
1850 // of writes to disk when using a QNetworkDiskCache (i.e. don't
1851 // write to disk when only the date changes).
1852 // However, without the date we cannot calculate the age of the page
1853 // anymore.
1854 //if (header == "date")
1855 //continue;
1856
1857 // Don't store Warning 1xx headers
1858 if (name.compare("warning", Qt::CaseInsensitive) == 0) {
1859 if (value.size() == 3
1860 && value[0] == '1'
1861 && isAsciiDigit(value[1])
1862 && isAsciiDigit(value[2]))
1863 continue;
1864 }
1865
1866 if (cacheHeaders.contains(name)) {
1867 // Match the behavior of Firefox and assume Cache-Control: "no-transform"
1868 constexpr QByteArrayView headers[]=
1869 {"content-encoding", "content-range", "content-type"};
1870 if (std::any_of(std::begin(headers), std::end(headers), caseInsensitiveCompare(name)))
1871 continue;
1872 }
1873
1874 // IIS has been known to send "Content-Length: 0" on 304 responses, so
1875 // ignore this too
1876 if (statusCode == 304 && name.compare("content-length", Qt::CaseInsensitive) == 0)
1877 continue;
1878
1879#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1880 QByteArrayView n = newHeaders.value(name);
1881 QByteArrayView o = cacheHeaders.value(name);
1882 if (n != o && name.compare("date", Qt::CaseInsensitive) != 0) {
1883 qDebug() << "replacing" << name;
1884 qDebug() << "new" << n;
1885 qDebug() << "old" << o;
1886 }
1887#endif
1888 cacheHeaders.replaceOrAppend(name, value);
1889 }
1890 metaData.setHeaders(cacheHeaders);
1891
1892 bool checkExpired = true;
1893
1894 QHash<QByteArray, QByteArray> cacheControl;
1895 auto value = cacheHeaders.value(QHttpHeaders::WellKnownHeader::CacheControl);
1896 if (!value.empty()) {
1897 cacheControl = parseHttpOptionHeader(value);
1898 QByteArray maxAge = cacheControl.value("max-age"_ba);
1899 if (!maxAge.isEmpty()) {
1900 checkExpired = false;
1901 QDateTime dt = QDateTime::currentDateTimeUtc();
1902 dt = dt.addSecs(maxAge.toInt());
1903 metaData.setExpirationDate(dt);
1904 }
1905 }
1906 if (checkExpired) {
1907 if (const auto value = cacheHeaders.value(
1908 QHttpHeaders::WellKnownHeader::Expires); !value.isEmpty()) {
1909 QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(value);
1910 metaData.setExpirationDate(expiredDateTime);
1911 }
1912 }
1913
1914 if (const auto value = cacheHeaders.value(
1915 QHttpHeaders::WellKnownHeader::LastModified); !value.isEmpty()) {
1916 metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(value));
1917 }
1918
1919
1920 bool canDiskCache;
1921 // only cache GET replies by default, all other replies (POST, PUT, DELETE)
1922 // are not cacheable by default (according to RFC 2616 section 9)
1923 if (httpRequest.operation() == QHttpNetworkRequest::Get) {
1924
1925 canDiskCache = true;
1926 // HTTP/1.1. Check the Cache-Control header
1927 if (cacheControl.contains("no-store"_ba))
1928 canDiskCache = false;
1929
1930 // responses to POST might be cacheable
1931 } else if (httpRequest.operation() == QHttpNetworkRequest::Post) {
1932
1933 canDiskCache = false;
1934 // some pages contain "expires:" and "cache-control: no-cache" field,
1935 // so we only might cache POST requests if we get "cache-control: max-age ..."
1936 if (cacheControl.contains("max-age"_ba))
1937 canDiskCache = true;
1938
1939 // responses to PUT and DELETE are not cacheable
1940 } else {
1941 canDiskCache = false;
1942 }
1943
1944 metaData.setSaveToDisk(canDiskCache);
1945 QNetworkCacheMetaData::AttributesMap attributes;
1946 if (statusCode != 304) {
1947 // update the status code
1948 attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1949 attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
1950 } else {
1951 // this is a redirection, keep the attributes intact
1952 attributes = oldMetaData.attributes();
1953 }
1954 metaData.setAttributes(attributes);
1955 return metaData;
1956}
1957
1959{
1960 Q_Q(const QNetworkReplyHttpImpl);
1961
1962 // Only GET operation supports resuming.
1963 if (operation != QNetworkAccessManager::GetOperation)
1964 return false;
1965
1966 const auto h = q->headers();
1967
1968 // Can only resume if server/resource supports Range header.
1969 const auto acceptRanges = h.value(QHttpHeaders::WellKnownHeader::AcceptRanges);
1970 if (acceptRanges.empty() || acceptRanges == "none")
1971 return false;
1972
1973 // We only support resuming for byte ranges.
1974 const auto range = h.value(QHttpHeaders::WellKnownHeader::Range);
1975 if (!range.empty()) {
1976 if (!range.startsWith(bytesEqualPrefix()))
1977 return false;
1978 }
1979
1980 // If we're using a download buffer then we don't support resuming/migration
1981 // right now. Too much trouble.
1983 return false;
1984
1985 return true;
1986}
1987
1989{
1990 resumeOffset = offset;
1991}
1992
1994{
1995 // Ensure this function is only being called once, and not at all if we were
1996 // cancelled
1997 if (state >= Working)
1998 return;
1999
2000 state = Working;
2001
2002 postRequest(request);
2003
2005 if (synchronous) {
2006 state = Finished;
2007 q_func()->setFinished(true);
2008 }
2009}
2010
2012{
2013 Q_Q(QNetworkReplyHttpImpl);
2014
2015 if (state != Working)
2016 return;
2017 if (!cacheLoadDevice || !q->isOpen() || !cacheLoadDevice->bytesAvailable())
2018 return;
2019
2020 // FIXME Optimize to use zerocopy download buffer if it is a QBuffer.
2021 // Needs to be done where sendCacheContents() (?) of HTTP is emitting
2022 // metaDataChanged ?
2023
2024 const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
2025 headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
2026
2027 // emit readyRead before downloadProgress in case this will cause events to be
2028 // processed and we get into a recursive call (as in QProgressDialog).
2029
2030 if (!(isHttpRedirectResponse())) {
2031 // This readyRead() goes to the user. The user then may or may not read() anything.
2032 emit q->readyRead();
2033
2034 if (downloadProgressSignalChoke.isValid() &&
2035 downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
2036 downloadProgressSignalChoke.start();
2037 emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1));
2038 }
2039 }
2040
2041 // A signal we've emitted might be handled by a slot that aborts,
2042 // so we need to check for that and bail out if it's happened:
2043 if (!q->isOpen())
2044 return;
2045
2046 // If there are still bytes available in the cacheLoadDevice then the user did not read
2047 // in response to the readyRead() signal. This means we have to load from the cacheLoadDevice
2048 // and buffer that stuff. This is needed to be able to properly emit finished() later.
2049 while (cacheLoadDevice->bytesAvailable() && !isHttpRedirectResponse())
2050 buffer.append(cacheLoadDevice->readAll());
2051
2052 if (cacheLoadDevice->isSequential()) {
2053 // check if end and we can read the EOF -1
2054 char c;
2055 qint64 actualCount = cacheLoadDevice->read(&c, 1);
2056 if (actualCount < 0) {
2057 cacheLoadDevice->deleteLater();
2058 cacheLoadDevice = nullptr;
2059 QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
2060 } else if (actualCount == 1) {
2061 // This is most probably not happening since most QIODevice returned something proper for bytesAvailable()
2062 // and had already been "emptied".
2063 cacheLoadDevice->ungetChar(c);
2064 }
2065 } else if ((!cacheLoadDevice->isSequential() && cacheLoadDevice->atEnd())) {
2066 // This codepath is in case the cache device is a QBuffer, e.g. from QNetworkDiskCache.
2067 cacheLoadDevice->deleteLater();
2068 cacheLoadDevice = nullptr;
2069 QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
2070 }
2071}
2072
2073
2075{
2076 Q_Q(QNetworkReplyHttpImpl);
2077
2078 // make sure this is only called once, ever.
2079 //_q_bufferOutgoingData may call it or the readChannelFinished emission
2080 if (state != Buffering)
2081 return;
2082
2083 // disconnect signals
2084 QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
2085 QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
2086
2087 // finally, start the request
2088 QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
2089}
2090
2092{
2093 // do not keep a dangling pointer to the device around (device
2094 // is closing because e.g. QAbstractNetworkCache::remove() was called).
2095 cacheSaveDevice = nullptr;
2096}
2097
2099{
2100 Q_Q(QNetworkReplyHttpImpl);
2101
2102 if (!outgoingDataBuffer) {
2103 // first call, create our buffer
2104 outgoingDataBuffer = std::make_shared<QRingBuffer>();
2105
2106 QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
2107 QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
2108 }
2109
2110 qint64 bytesBuffered = 0;
2111 qint64 bytesToBuffer = 0;
2112
2113 // read data into our buffer
2114 forever {
2115 bytesToBuffer = outgoingData->bytesAvailable();
2116 // unknown? just try 2 kB, this also ensures we always try to read the EOF
2117 if (bytesToBuffer <= 0)
2118 bytesToBuffer = 2*1024;
2119
2120 char *dst = outgoingDataBuffer->reserve(bytesToBuffer);
2121 bytesBuffered = outgoingData->read(dst, bytesToBuffer);
2122
2123 if (bytesBuffered == -1) {
2124 // EOF has been reached.
2125 outgoingDataBuffer->chop(bytesToBuffer);
2126
2127 _q_bufferOutgoingDataFinished();
2128 break;
2129 } else if (bytesBuffered == 0) {
2130 // nothing read right now, just wait until we get called again
2131 outgoingDataBuffer->chop(bytesToBuffer);
2132
2133 break;
2134 } else {
2135 // don't break, try to read() again
2136 outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered);
2137 }
2138 }
2139}
2140
2142{
2143 Q_Q(QNetworkReplyHttpImpl);
2144 q->abortImpl(QNetworkReply::TimeoutError);
2145}
2146
2148{
2149 Q_Q(QNetworkReplyHttpImpl);
2150 if (!transferTimeout) {
2151 transferTimeout = new QTimer(q);
2152 QObject::connect(transferTimeout, SIGNAL(timeout()),
2153 q, SLOT(_q_transferTimedOut()),
2154 Qt::QueuedConnection);
2155 }
2156 transferTimeout->stop();
2157 if (request.transferTimeoutAsDuration() > 0ms) {
2158 transferTimeout->setSingleShot(true);
2159 transferTimeout->setInterval(request.transferTimeoutAsDuration());
2160 QMetaObject::invokeMethod(transferTimeout, "start",
2161 Qt::QueuedConnection);
2162
2163 }
2164}
2165
2166// need to have this function since the reply is a private member variable
2167// and the special backends need to access this.
2168void QNetworkReplyHttpImplPrivate::emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal)
2169{
2170 Q_Q(QNetworkReplyHttpImpl);
2171 if (isFinished)
2172 return;
2173
2175
2176 if (!emitAllUploadProgressSignals) {
2177 //choke signal emissions, except the first and last signals which are unconditional
2178 if (uploadProgressSignalChoke.isValid()) {
2179 if (bytesSent != bytesTotal && uploadProgressSignalChoke.elapsed() < progressSignalInterval) {
2180 return;
2181 }
2182 }
2183 uploadProgressSignalChoke.start();
2184 }
2185 emit q->uploadProgress(bytesSent, bytesTotal);
2186}
2187
2189{
2190 Q_Q(QNetworkReplyHttpImpl);
2191
2192 if (outgoingDataBuffer)
2193 uploadByteDevice = QNonContiguousByteDeviceFactory::createShared(outgoingDataBuffer);
2194 else if (outgoingData) {
2195 uploadByteDevice = QNonContiguousByteDeviceFactory::createShared(outgoingData);
2196 } else {
2197 return nullptr;
2198 }
2199
2200 // We want signal emissions only for normal asynchronous uploads
2201 if (!synchronous)
2202 QObject::connect(uploadByteDevice.get(), SIGNAL(readProgress(qint64,qint64)),
2203 q, SLOT(emitReplyUploadProgress(qint64,qint64)));
2204
2205 return uploadByteDevice.get();
2206}
2207
2209{
2210 // This gets called queued, just forward to real call then
2211 finished();
2212}
2213
2215{
2216 Q_Q(QNetworkReplyHttpImpl);
2217 if (transferTimeout)
2218 transferTimeout->stop();
2219 if (state == Finished || state == Aborted)
2220 return;
2221
2222 const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
2223 headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
2224 const qint64 totalSize = totalSizeOpt.value_or(-1);
2225
2226 // if we don't know the total size of or we received everything save the cache.
2227 // If the data is compressed then this is done in readData()
2228 if ((totalSize == -1 || bytesDownloaded == totalSize)
2229 && !decompressHelper.isValid()) {
2231 }
2232
2233 // We check for errorCode too as in case of SSL handshake failure, we still
2234 // get the HTTP redirect status code (301, 303 etc)
2235 if (isHttpRedirectResponse() && errorCode == QNetworkReply::NoError)
2236 return;
2237
2238 state = Finished;
2239 q->setFinished(true);
2240
2241 if (totalSize == -1) {
2242 emit q->downloadProgress(bytesDownloaded, bytesDownloaded);
2243 } else {
2244 emit q->downloadProgress(bytesDownloaded, totalSize);
2245 }
2246
2247 if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
2248 emit q->uploadProgress(0, 0);
2249
2250 emit q->readChannelFinished();
2251 emit q->finished();
2252}
2253
2254void QNetworkReplyHttpImplPrivate::_q_error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
2255{
2256 this->error(code, errorMessage);
2257}
2258
2259
2260void QNetworkReplyHttpImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
2261{
2262 Q_Q(QNetworkReplyHttpImpl);
2263 // Can't set and emit multiple errors.
2264 if (errorCode != QNetworkReply::NoError) {
2265 // But somewhat unavoidable if we have cancelled the request:
2266 if (errorCode != QNetworkReply::OperationCanceledError
2267 && errorCode != QNetworkReply::TimeoutError) {
2268 qWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.");
2269 }
2270 return;
2271 }
2272
2273 errorCode = code;
2274 q->setErrorString(errorMessage);
2275
2276 // note: might not be a good idea, since users could decide to delete us
2277 // which would delete the backend too...
2278 // maybe we should protect the backend
2279 emit q->errorOccurred(code);
2280}
2281
2283{
2284 // FIXME merge this with replyDownloadMetaData(); ?
2285
2286 Q_Q(QNetworkReplyHttpImpl);
2287 // 1. do we have cookies?
2288 // 2. are we allowed to set them?
2289 Q_ASSERT(manager);
2290
2291 const auto cookiesOpt = QNetworkHeadersPrivate::toSetCookieList(
2292 headers().values(QHttpHeaders::WellKnownHeader::SetCookie));
2293 const auto cookies = cookiesOpt.value_or(QList<QNetworkCookie>());
2294 if (!cookies.empty()
2295 && request.attribute(QNetworkRequest::CookieSaveControlAttribute,
2296 QNetworkRequest::Automatic).toInt() == QNetworkRequest::Automatic) {
2297 QNetworkCookieJar *jar = manager->cookieJar();
2298 if (jar) {
2299 jar->setCookiesFromUrl(cookies, url);
2300 }
2301 }
2302 emit q->metaDataChanged();
2303}
2304
2306{
2307 // check if we can save and if we're allowed to
2308 if (!managerPrivate->networkCache
2309 || !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool())
2310 return;
2311 cacheEnabled = true;
2312}
2313
2315{
2316 return (cacheEnabled && managerPrivate->networkCache != nullptr);
2317}
2318
2320{
2321 if (!enable && !cacheEnabled)
2322 return; // nothing to do
2323 if (enable && cacheEnabled)
2324 return; // nothing to do either!
2325
2326 if (enable) {
2327 if (Q_UNLIKELY(bytesDownloaded)) {
2328 qDebug() << "setCachingEnabled: " << bytesDownloaded << " bytesDownloaded";
2329 // refuse to enable in this case
2330 qCritical("QNetworkReplyImpl: backend error: caching was enabled after some bytes had been written");
2331 return;
2332 }
2333
2335 } else {
2336 // someone told us to turn on, then back off?
2337 // ok... but you should make up your mind
2338 qDebug("QNetworkReplyImpl: setCachingEnabled(true) called after setCachingEnabled(false)");
2339 managerPrivate->networkCache->remove(url);
2340 cacheSaveDevice = nullptr;
2341 cacheEnabled = false;
2342 }
2343}
2344
2346{
2347 return operation == QNetworkAccessManager::GetOperation || operation == QNetworkAccessManager::HeadOperation;
2348}
2349
2351{
2352 if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) {
2353 managerPrivate->networkCache->remove(url);
2354 } else if (cacheEnabled && cacheSaveDevice) {
2355 managerPrivate->networkCache->insert(cacheSaveDevice);
2356 }
2357 cacheSaveDevice = nullptr;
2358 cacheEnabled = false;
2359}
2360
2361QT_END_NAMESPACE
2362
2363#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)