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
1347 const bool getOperationKeepsBody = (operation == QNetworkAccessManager::GetOperation)
1348 && httpStatus == 308;
1349
1350 redirectRequest = createRedirectRequest(originalRequest, url, maxRedirectsRemaining);
1351 operation = getRedirectOperation(operation, httpStatus);
1352
1353 // Clear stale headers, the relevant ones get set again later
1354 httpRequest.clearHeaders();
1355 auto newHeaders = redirectRequest.headers();
1356 if ((operation == QNetworkAccessManager::GetOperation
1357 || operation == QNetworkAccessManager::HeadOperation) && !getOperationKeepsBody) {
1358 // possibly changed from not-GET/HEAD to GET/HEAD, make sure to get rid of upload device
1359 uploadByteDevice.reset();
1360 uploadByteDevicePosition = 0;
1361 if (outgoingData) {
1362 QObject::disconnect(outgoingData, SIGNAL(readyRead()), q,
1363 SLOT(_q_bufferOutgoingData()));
1364 QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q,
1365 SLOT(_q_bufferOutgoingDataFinished()));
1366 }
1367 outgoingData = nullptr;
1368 outgoingDataBuffer.reset();
1369 // We need to explicitly unset these headers so they're not reapplied to the httpRequest
1370 newHeaders.removeAll(QHttpHeaders::WellKnownHeader::ContentLength);
1371 newHeaders.removeAll(QHttpHeaders::WellKnownHeader::ContentType);
1372 }
1373
1374 if (const QNetworkCookieJar *const cookieJar = manager->cookieJar()) {
1375 auto cookies = cookieJar->cookiesForUrl(url);
1376 if (!cookies.empty()) {
1377 auto cookieHeader = QNetworkHeadersPrivate::fromCookieList(cookies);
1378 newHeaders.replaceOrAppend(QHttpHeaders::WellKnownHeader::Cookie, cookieHeader);
1379 }
1380 }
1381
1382 redirectRequest.setHeaders(std::move(newHeaders));
1383
1384 if (httpRequest.redirectPolicy() != QNetworkRequest::UserVerifiedRedirectPolicy)
1386
1387 emit q->redirected(url);
1388}
1389
1391{
1392 Q_Q(QNetworkReplyHttpImpl);
1393 Q_ASSERT(managerPrivate);
1394
1395 decompressHelper.clear();
1396 clearHeaders();
1397
1398 if (managerPrivate->thread)
1399 managerPrivate->thread->disconnect();
1400
1401 QMetaObject::invokeMethod(
1402 q, [this]() { postRequest(redirectRequest); }, Qt::QueuedConnection);
1403}
1404
1405static constexpr QLatin1StringView locationHeader() noexcept { return "location"_L1; }
1406
1408{
1409 Q_Q(QNetworkReplyHttpImpl);
1410 switch (statusCode) {
1411 case 301: // Moved Permanently
1412 case 302: // Found
1413 case 303: // See Other
1414 case 307: // Temporary Redirect
1415 case 308: // Permanent Redirect
1416 // What do we do about the caching of the HTML note?
1417 // The response to a 303 MUST NOT be cached, while the response to
1418 // all of the others is cacheable if the headers indicate it to be
1419 QByteArrayView header = q->headers().value(locationHeader());
1420 QUrl url = QUrl(QString::fromUtf8(header));
1421 if (!url.isValid())
1422 url = QUrl(QLatin1StringView(header));
1423 q->setAttribute(QNetworkRequest::RedirectionTargetAttribute, url);
1424 }
1425}
1426
1427void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QHttpHeaders &hm,
1428 int sc, const QString &rp, bool pu,
1429 QSharedPointer<char> db,
1430 qint64 contentLength,
1431 qint64 removedContentLength,
1432 bool h2Used, bool isCompressed)
1433{
1434 Q_Q(QNetworkReplyHttpImpl);
1435 Q_UNUSED(contentLength);
1436
1437 statusCode = sc;
1438 reasonPhrase = rp;
1439
1440#ifndef QT_NO_SSL
1441 // We parse this header only if we're using secure transport:
1442 //
1443 // RFC6797, 8.1
1444 // If an HTTP response is received over insecure transport, the UA MUST
1445 // ignore any present STS header field(s).
1446 if (url.scheme() == "https"_L1 && managerPrivate->stsEnabled)
1447 managerPrivate->stsCache.updateFromHeaders(hm, url);
1448#endif
1449 // Download buffer
1450 if (!db.isNull()) {
1451 downloadBufferPointer = db;
1452 downloadZerocopyBuffer = downloadBufferPointer.data();
1453 downloadBufferCurrentSize = 0;
1454 q->setAttribute(QNetworkRequest::DownloadBufferAttribute, QVariant::fromValue<QSharedPointer<char> > (downloadBufferPointer));
1455 }
1456
1457 q->setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, pu);
1458 q->setAttribute(QNetworkRequest::Http2WasUsedAttribute, h2Used);
1459
1460 // A user having manually defined which encodings they accept is, for
1461 // somwehat unknown (presumed legacy compatibility) reasons treated as
1462 // disabling our decompression:
1463 const bool autoDecompress = !request.headers().contains(QHttpHeaders::WellKnownHeader::AcceptEncoding);
1464 const bool shouldDecompress = isCompressed && autoDecompress;
1465 // reconstruct the HTTP header
1466 auto h = q->headers();
1467 for (qsizetype i = 0; i < hm.size(); ++i) {
1468 const auto key = hm.nameAt(i);
1469 const auto originValue = hm.valueAt(i);
1470
1471 // Reset any previous "location" header set in the reply. In case of
1472 // redirects, we don't want to 'append' multiple location header values,
1473 // rather we keep only the latest one
1474 if (key.compare(locationHeader(), Qt::CaseInsensitive) == 0)
1475 h.removeAll(key);
1476
1477 if (shouldDecompress && !decompressHelper.isValid() && key == "content-encoding"_L1) {
1478 if (!synchronous) // with synchronous all the data is expected to be handled at once
1479 decompressHelper.setCountingBytesEnabled(true);
1480
1481 if (!decompressHelper.setEncoding(originValue)) {
1482 error(QNetworkReplyImpl::NetworkError::UnknownContentError,
1483 QCoreApplication::translate("QHttp", "Failed to initialize decompression: %1")
1484 .arg(decompressHelper.errorString()));
1485 return;
1486 }
1487 decompressHelper.setDecompressedSafetyCheckThreshold(
1488 request.decompressedSafetyCheckThreshold());
1489 }
1490
1491 h.append(key, originValue);
1492 }
1493 q->setHeaders(std::move(h));
1494
1495 q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1496 q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
1497 if (removedContentLength != -1)
1498 q->setAttribute(QNetworkRequest::OriginalContentLengthAttribute, removedContentLength);
1499
1500 // is it a redirection?
1503
1504 if (statusCode >= 500 && statusCode < 600) {
1505 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1506 if (nc) {
1507 QNetworkCacheMetaData metaData = nc->metaData(httpRequest.url());
1508 auto value = metaData.headers().value(QHttpHeaders::WellKnownHeader::CacheControl);
1509 bool mustReValidate = false;
1510 if (!value.empty()) {
1511 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(value);
1512 if (cacheControl.contains("must-revalidate"_ba))
1513 mustReValidate = true;
1514 }
1515 if (!mustReValidate && sendCacheContents(metaData))
1516 return;
1517 }
1518 }
1519
1520 if (statusCode == 304) {
1521#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1522 qDebug() << "Received a 304 from" << request.url();
1523#endif
1524 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1525 if (nc) {
1526 QNetworkCacheMetaData oldMetaData = nc->metaData(httpRequest.url());
1527 QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData);
1528 if (oldMetaData != metaData)
1529 nc->updateMetaData(metaData);
1530 if (sendCacheContents(metaData))
1531 return;
1532 }
1533 }
1534
1535
1536 if (statusCode != 304 && statusCode != 303) {
1537 if (!isCachingEnabled())
1539 }
1540
1542}
1543
1544void QNetworkReplyHttpImplPrivate::replyDownloadProgressSlot(qint64 bytesReceived, qint64 bytesTotal)
1545{
1546 Q_Q(QNetworkReplyHttpImpl);
1547
1548 // If we're closed just ignore this data
1549 if (!q->isOpen())
1550 return;
1551
1552 // we can be sure here that there is a download buffer
1553
1554 int pendingSignals = (int)pendingDownloadProgressEmissions->fetchAndAddAcquire(-1) - 1;
1555 if (pendingSignals > 0) {
1556 // Let's ignore this signal and look at the next one coming in
1557 // (signal comppression)
1558 return;
1559 }
1560
1561 if (!q->isOpen())
1562 return;
1563
1564 if (cacheEnabled && isCachingAllowed() && bytesReceived == bytesTotal) {
1565 // Write everything in one go if we use a download buffer. might be more performant.
1567 // need to check again if cache enabled and device exists
1569 cacheSaveDevice->write(downloadZerocopyBuffer, bytesTotal);
1570 // FIXME where is it closed?
1571 }
1572
1574 return;
1575
1576 bytesDownloaded = bytesReceived;
1578
1579 downloadBufferCurrentSize = bytesReceived;
1580
1581 // Only emit readyRead when actual data is there
1582 // emit readyRead before downloadProgress in case this will cause events to be
1583 // processed and we get into a recursive call (as in QProgressDialog).
1584 if (bytesDownloaded > 0)
1585 emit q->readyRead();
1586 if (downloadProgressSignalChoke.isValid() &&
1587 downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
1588 downloadProgressSignalChoke.start();
1589 emit q->downloadProgress(bytesDownloaded, bytesTotal);
1590 }
1591}
1592
1593void QNetworkReplyHttpImplPrivate::httpAuthenticationRequired(const QHttpNetworkRequest &request,
1594 QAuthenticator *auth)
1595{
1596 managerPrivate->authenticationRequired(auth, q_func(), synchronous, url, &urlForLastAuthentication, request.withCredentials());
1597}
1598
1599#ifndef QT_NO_NETWORKPROXY
1600void QNetworkReplyHttpImplPrivate::proxyAuthenticationRequired(const QNetworkProxy &proxy,
1601 QAuthenticator *authenticator)
1602{
1603 managerPrivate->proxyAuthenticationRequired(request.url(), proxy, synchronous, authenticator, &lastProxyAuthentication);
1604}
1605#endif
1606
1607void QNetworkReplyHttpImplPrivate::httpError(QNetworkReply::NetworkError errorCode,
1608 const QString &errorString)
1609{
1610#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1611 qDebug() << "http error!" << errorCode << errorString;
1612#endif
1613
1614 // FIXME?
1615 error(errorCode, errorString);
1616}
1617
1618#ifndef QT_NO_SSL
1620{
1621 Q_Q(QNetworkReplyHttpImpl);
1622 emit q->encrypted();
1623}
1624
1626 const QList<QSslError> &list, bool *ignoreAll, QList<QSslError> *toBeIgnored)
1627{
1628 Q_Q(QNetworkReplyHttpImpl);
1629 emit q->sslErrors(list);
1630 // Check if the callback set any ignore and return this here to http thread
1632 *ignoreAll = true;
1633 if (!pendingIgnoreSslErrorsList.isEmpty())
1634 *toBeIgnored = pendingIgnoreSslErrorsList;
1635}
1636
1637void QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfiguration &newSslConfiguration)
1638{
1639 // Receiving the used SSL configuration from the HTTP thread
1640 if (sslConfiguration)
1641 *sslConfiguration = newSslConfiguration;
1642 else
1643 sslConfiguration.reset(new QSslConfiguration(newSslConfiguration));
1644}
1645
1647{
1648 Q_Q(QNetworkReplyHttpImpl);
1649 emit q->preSharedKeyAuthenticationRequired(authenticator);
1650}
1651#endif
1652
1653// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1655{
1656 *r = uploadByteDevice->reset();
1657 if (*r) {
1658 // reset our own position which is used for the inter-thread communication
1659 uploadByteDevicePosition = 0;
1660 }
1661}
1662
1663// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1664void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 pos, qint64 amount)
1665{
1666 if (!uploadByteDevice) // uploadByteDevice is no longer available
1667 return;
1668
1669 if (uploadByteDevicePosition + amount != pos) {
1670 // Sanity check, should not happen.
1671 error(QNetworkReply::UnknownNetworkError, QString());
1672 return;
1673 }
1674 uploadByteDevice->advanceReadPointer(amount);
1675 uploadByteDevicePosition += amount;
1676}
1677
1678// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
1680{
1681 Q_Q(QNetworkReplyHttpImpl);
1682
1683 if (!uploadByteDevice) // uploadByteDevice is no longer available
1684 return;
1685
1686 // call readPointer
1687 qint64 currentUploadDataLength = 0;
1688 char *data = const_cast<char*>(uploadByteDevice->readPointer(maxSize, currentUploadDataLength));
1689
1690 if (currentUploadDataLength == 0) {
1691 uploadDeviceChoking = true;
1692 // No bytes from upload byte device. There will be bytes later, it will emit readyRead()
1693 // and our uploadByteDeviceReadyReadSlot() is called.
1694 return;
1695 } else {
1696 uploadDeviceChoking = false;
1697 }
1698
1699 // Let's make a copy of this data
1700 QByteArray dataArray(data, currentUploadDataLength);
1701
1702 // Communicate back to HTTP thread
1703 emit q->haveUploadData(uploadByteDevicePosition, dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
1704}
1705
1707{
1708 // Start the flow between this thread and the HTTP thread again by triggering a upload.
1709 // However only do this when we were choking before, else the state in
1710 // QNonContiguousByteDeviceThreadForwardImpl gets messed up.
1711 if (uploadDeviceChoking) {
1712 uploadDeviceChoking = false;
1713 wantUploadDataSlot(1024);
1714 }
1715}
1716
1717
1718/*
1719 A simple web page that can be used to test us: http://www.procata.com/cachetest/
1720 */
1721bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData &metaData)
1722{
1723 Q_Q(QNetworkReplyHttpImpl);
1724
1726 if (!metaData.isValid())
1727 return false;
1728
1729 QAbstractNetworkCache *nc = managerPrivate->networkCache;
1730 Q_ASSERT(nc);
1731 QIODevice *contents = nc->data(url);
1732 if (!contents) {
1733#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1734 qDebug() << "Cannot send cache, the contents are 0" << url;
1735#endif
1736 return false;
1737 }
1738 contents->setParent(q);
1739
1740 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
1741 int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt();
1742 if (status < 100)
1743 status = 200; // fake it
1744
1745 statusCode = status;
1746
1747 q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
1748 q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
1749 q->setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
1750
1751 QHttpHeaders cachedHeaders = metaData.headers();
1752 QHttpHeaders h = headers();
1753 QUrl redirectUrl;
1754 for (qsizetype i = 0; i < cachedHeaders.size(); ++i) {
1755 const auto name = cachedHeaders.nameAt(i);
1756 const auto value = cachedHeaders.valueAt(i);
1757
1758 if (httpRequest.isFollowRedirects()
1759 && !name.compare(locationHeader(), Qt::CaseInsensitive)) {
1760 redirectUrl = QUrl::fromEncoded(value);
1761 }
1762
1763 h.replaceOrAppend(name, value);
1764 }
1765 setHeaders(std::move(h));
1766
1768 checkForRedirect(status);
1769
1770 cacheLoadDevice = contents;
1771 q->connect(cacheLoadDevice, SIGNAL(readyRead()), SLOT(_q_cacheLoadReadyRead()));
1772 q->connect(cacheLoadDevice, SIGNAL(readChannelFinished()), SLOT(_q_cacheLoadReadyRead()));
1773
1774 // This needs to be emitted in the event loop because it can be reached at
1775 // the direct code path of qnam.get(...) before the user has a chance
1776 // to connect any signals.
1777 QMetaObject::invokeMethod(q, "_q_metaDataChanged", Qt::QueuedConnection);
1778 QMetaObject::invokeMethod(q, "_q_cacheLoadReadyRead", Qt::QueuedConnection);
1779
1780
1781#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1782 qDebug() << "Successfully sent cache:" << url << contents->size() << "bytes";
1783#endif
1784
1785 // Do redirect processing
1786 if (httpRequest.isFollowRedirects() && QHttpNetworkReply::isHttpRedirect(status)) {
1787 QMetaObject::invokeMethod(q, "onRedirected", Qt::QueuedConnection,
1788 Q_ARG(QUrl, redirectUrl),
1789 Q_ARG(int, status),
1790 Q_ARG(int, httpRequest.redirectCount() - 1));
1791 }
1792
1793 // Set the following flag so we can ignore some signals from HTTP thread
1794 // that would still come
1795 loadingFromCache = true;
1796 return true;
1797}
1798
1799static auto caseInsensitiveCompare(QByteArrayView value)
1800{
1801 return [value](QByteArrayView element)
1802 {
1803 return value.compare(element, Qt::CaseInsensitive) == 0;
1804 };
1805}
1806
1807static bool isHopByHop(QByteArrayView header)
1808{
1809 constexpr QByteArrayView headers[] = { "connection",
1810 "keep-alive",
1811 "proxy-authenticate",
1812 "proxy-authorization",
1813 "te",
1814 "trailers",
1815 "transfer-encoding",
1816 "upgrade"};
1817 return std::any_of(std::begin(headers), std::end(headers), caseInsensitiveCompare(header));
1818}
1819
1820QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const
1821{
1822 Q_Q(const QNetworkReplyHttpImpl);
1823
1824 QNetworkCacheMetaData metaData = oldMetaData;
1825 QHttpHeaders cacheHeaders = metaData.headers();
1826
1827 const auto newHeaders = q->headers();
1828 for (qsizetype i = 0; i < newHeaders.size(); ++i) {
1829 const auto name = newHeaders.nameAt(i);
1830 const auto value = newHeaders.valueAt(i);
1831
1832 if (isHopByHop(name))
1833 continue;
1834
1835 if (name.compare("set-cookie", Qt::CaseInsensitive) == 0)
1836 continue;
1837
1838 // for 4.6.0, we were planning to not store the date header in the
1839 // cached resource; through that we planned to reduce the number
1840 // of writes to disk when using a QNetworkDiskCache (i.e. don't
1841 // write to disk when only the date changes).
1842 // However, without the date we cannot calculate the age of the page
1843 // anymore.
1844 //if (header == "date")
1845 //continue;
1846
1847 // Don't store Warning 1xx headers
1848 if (name.compare("warning", Qt::CaseInsensitive) == 0) {
1849 if (value.size() == 3
1850 && value[0] == '1'
1851 && isAsciiDigit(value[1])
1852 && isAsciiDigit(value[2]))
1853 continue;
1854 }
1855
1856 if (cacheHeaders.contains(name)) {
1857 // Match the behavior of Firefox and assume Cache-Control: "no-transform"
1858 constexpr QByteArrayView headers[]=
1859 {"content-encoding", "content-range", "content-type"};
1860 if (std::any_of(std::begin(headers), std::end(headers), caseInsensitiveCompare(name)))
1861 continue;
1862 }
1863
1864 // IIS has been known to send "Content-Length: 0" on 304 responses, so
1865 // ignore this too
1866 if (statusCode == 304 && name.compare("content-length", Qt::CaseInsensitive) == 0)
1867 continue;
1868
1869#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1870 QByteArrayView n = newHeaders.value(name);
1871 QByteArrayView o = cacheHeaders.value(name);
1872 if (n != o && name.compare("date", Qt::CaseInsensitive) != 0) {
1873 qDebug() << "replacing" << name;
1874 qDebug() << "new" << n;
1875 qDebug() << "old" << o;
1876 }
1877#endif
1878 cacheHeaders.replaceOrAppend(name, value);
1879 }
1880 metaData.setHeaders(cacheHeaders);
1881
1882 bool checkExpired = true;
1883
1884 QHash<QByteArray, QByteArray> cacheControl;
1885 auto value = cacheHeaders.value(QHttpHeaders::WellKnownHeader::CacheControl);
1886 if (!value.empty()) {
1887 cacheControl = parseHttpOptionHeader(value);
1888 QByteArray maxAge = cacheControl.value("max-age"_ba);
1889 if (!maxAge.isEmpty()) {
1890 checkExpired = false;
1891 QDateTime dt = QDateTime::currentDateTimeUtc();
1892 dt = dt.addSecs(maxAge.toInt());
1893 metaData.setExpirationDate(dt);
1894 }
1895 }
1896 if (checkExpired) {
1897 if (const auto value = cacheHeaders.value(
1898 QHttpHeaders::WellKnownHeader::Expires); !value.isEmpty()) {
1899 QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(value);
1900 metaData.setExpirationDate(expiredDateTime);
1901 }
1902 }
1903
1904 if (const auto value = cacheHeaders.value(
1905 QHttpHeaders::WellKnownHeader::LastModified); !value.isEmpty()) {
1906 metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(value));
1907 }
1908
1909
1910 bool canDiskCache;
1911 // only cache GET replies by default, all other replies (POST, PUT, DELETE)
1912 // are not cacheable by default (according to RFC 2616 section 9)
1913 if (httpRequest.operation() == QHttpNetworkRequest::Get) {
1914
1915 canDiskCache = true;
1916 // HTTP/1.1. Check the Cache-Control header
1917 if (cacheControl.contains("no-store"_ba))
1918 canDiskCache = false;
1919
1920 // responses to POST might be cacheable
1921 } else if (httpRequest.operation() == QHttpNetworkRequest::Post) {
1922
1923 canDiskCache = false;
1924 // some pages contain "expires:" and "cache-control: no-cache" field,
1925 // so we only might cache POST requests if we get "cache-control: max-age ..."
1926 if (cacheControl.contains("max-age"_ba))
1927 canDiskCache = true;
1928
1929 // responses to PUT and DELETE are not cacheable
1930 } else {
1931 canDiskCache = false;
1932 }
1933
1934 metaData.setSaveToDisk(canDiskCache);
1935 QNetworkCacheMetaData::AttributesMap attributes;
1936 if (statusCode != 304) {
1937 // update the status code
1938 attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1939 attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
1940 } else {
1941 // this is a redirection, keep the attributes intact
1942 attributes = oldMetaData.attributes();
1943 }
1944 metaData.setAttributes(attributes);
1945 return metaData;
1946}
1947
1949{
1950 Q_Q(const QNetworkReplyHttpImpl);
1951
1952 // Only GET operation supports resuming.
1953 if (operation != QNetworkAccessManager::GetOperation)
1954 return false;
1955
1956 const auto h = q->headers();
1957
1958 // Can only resume if server/resource supports Range header.
1959 const auto acceptRanges = h.value(QHttpHeaders::WellKnownHeader::AcceptRanges);
1960 if (acceptRanges.empty() || acceptRanges == "none")
1961 return false;
1962
1963 // We only support resuming for byte ranges.
1964 const auto range = h.value(QHttpHeaders::WellKnownHeader::Range);
1965 if (!range.empty()) {
1966 if (!range.startsWith(bytesEqualPrefix()))
1967 return false;
1968 }
1969
1970 // If we're using a download buffer then we don't support resuming/migration
1971 // right now. Too much trouble.
1973 return false;
1974
1975 return true;
1976}
1977
1979{
1980 resumeOffset = offset;
1981}
1982
1984{
1985 // Ensure this function is only being called once, and not at all if we were
1986 // cancelled
1987 if (state >= Working)
1988 return;
1989
1990 state = Working;
1991
1992 postRequest(request);
1993
1995 if (synchronous) {
1996 state = Finished;
1997 q_func()->setFinished(true);
1998 }
1999}
2000
2002{
2003 Q_Q(QNetworkReplyHttpImpl);
2004
2005 if (state != Working)
2006 return;
2007 if (!cacheLoadDevice || !q->isOpen() || !cacheLoadDevice->bytesAvailable())
2008 return;
2009
2010 // FIXME Optimize to use zerocopy download buffer if it is a QBuffer.
2011 // Needs to be done where sendCacheContents() (?) of HTTP is emitting
2012 // metaDataChanged ?
2013
2014 const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
2015 headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
2016
2017 // emit readyRead before downloadProgress in case this will cause events to be
2018 // processed and we get into a recursive call (as in QProgressDialog).
2019
2020 if (!(isHttpRedirectResponse())) {
2021 // This readyRead() goes to the user. The user then may or may not read() anything.
2022 emit q->readyRead();
2023
2024 if (downloadProgressSignalChoke.isValid() &&
2025 downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
2026 downloadProgressSignalChoke.start();
2027 emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1));
2028 }
2029 }
2030
2031 // A signal we've emitted might be handled by a slot that aborts,
2032 // so we need to check for that and bail out if it's happened:
2033 if (!q->isOpen())
2034 return;
2035
2036 // If there are still bytes available in the cacheLoadDevice then the user did not read
2037 // in response to the readyRead() signal. This means we have to load from the cacheLoadDevice
2038 // and buffer that stuff. This is needed to be able to properly emit finished() later.
2039 while (cacheLoadDevice->bytesAvailable() && !isHttpRedirectResponse())
2040 buffer.append(cacheLoadDevice->readAll());
2041
2042 if (cacheLoadDevice->isSequential()) {
2043 // check if end and we can read the EOF -1
2044 char c;
2045 qint64 actualCount = cacheLoadDevice->read(&c, 1);
2046 if (actualCount < 0) {
2047 cacheLoadDevice->deleteLater();
2048 cacheLoadDevice = nullptr;
2049 QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
2050 } else if (actualCount == 1) {
2051 // This is most probably not happening since most QIODevice returned something proper for bytesAvailable()
2052 // and had already been "emptied".
2053 cacheLoadDevice->ungetChar(c);
2054 }
2055 } else if ((!cacheLoadDevice->isSequential() && cacheLoadDevice->atEnd())) {
2056 // This codepath is in case the cache device is a QBuffer, e.g. from QNetworkDiskCache.
2057 cacheLoadDevice->deleteLater();
2058 cacheLoadDevice = nullptr;
2059 QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
2060 }
2061}
2062
2063
2065{
2066 Q_Q(QNetworkReplyHttpImpl);
2067
2068 // make sure this is only called once, ever.
2069 //_q_bufferOutgoingData may call it or the readChannelFinished emission
2070 if (state != Buffering)
2071 return;
2072
2073 // disconnect signals
2074 QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
2075 QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
2076
2077 // finally, start the request
2078 QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
2079}
2080
2082{
2083 // do not keep a dangling pointer to the device around (device
2084 // is closing because e.g. QAbstractNetworkCache::remove() was called).
2085 cacheSaveDevice = nullptr;
2086}
2087
2089{
2090 Q_Q(QNetworkReplyHttpImpl);
2091
2092 if (!outgoingDataBuffer) {
2093 // first call, create our buffer
2094 outgoingDataBuffer = std::make_shared<QRingBuffer>();
2095
2096 QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
2097 QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
2098 }
2099
2100 qint64 bytesBuffered = 0;
2101 qint64 bytesToBuffer = 0;
2102
2103 // read data into our buffer
2104 forever {
2105 bytesToBuffer = outgoingData->bytesAvailable();
2106 // unknown? just try 2 kB, this also ensures we always try to read the EOF
2107 if (bytesToBuffer <= 0)
2108 bytesToBuffer = 2*1024;
2109
2110 char *dst = outgoingDataBuffer->reserve(bytesToBuffer);
2111 bytesBuffered = outgoingData->read(dst, bytesToBuffer);
2112
2113 if (bytesBuffered == -1) {
2114 // EOF has been reached.
2115 outgoingDataBuffer->chop(bytesToBuffer);
2116
2117 _q_bufferOutgoingDataFinished();
2118 break;
2119 } else if (bytesBuffered == 0) {
2120 // nothing read right now, just wait until we get called again
2121 outgoingDataBuffer->chop(bytesToBuffer);
2122
2123 break;
2124 } else {
2125 // don't break, try to read() again
2126 outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered);
2127 }
2128 }
2129}
2130
2132{
2133 Q_Q(QNetworkReplyHttpImpl);
2134 q->abortImpl(QNetworkReply::TimeoutError);
2135}
2136
2138{
2139 Q_Q(QNetworkReplyHttpImpl);
2140 if (!transferTimeout) {
2141 transferTimeout = new QTimer(q);
2142 QObject::connect(transferTimeout, SIGNAL(timeout()),
2143 q, SLOT(_q_transferTimedOut()),
2144 Qt::QueuedConnection);
2145 }
2146 transferTimeout->stop();
2147 if (request.transferTimeoutAsDuration() > 0ms) {
2148 transferTimeout->setSingleShot(true);
2149 transferTimeout->setInterval(request.transferTimeoutAsDuration());
2150 QMetaObject::invokeMethod(transferTimeout, "start",
2151 Qt::QueuedConnection);
2152
2153 }
2154}
2155
2156// need to have this function since the reply is a private member variable
2157// and the special backends need to access this.
2158void QNetworkReplyHttpImplPrivate::emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal)
2159{
2160 Q_Q(QNetworkReplyHttpImpl);
2161 if (isFinished)
2162 return;
2163
2165
2166 if (!emitAllUploadProgressSignals) {
2167 //choke signal emissions, except the first and last signals which are unconditional
2168 if (uploadProgressSignalChoke.isValid()) {
2169 if (bytesSent != bytesTotal && uploadProgressSignalChoke.elapsed() < progressSignalInterval) {
2170 return;
2171 }
2172 }
2173 uploadProgressSignalChoke.start();
2174 }
2175 emit q->uploadProgress(bytesSent, bytesTotal);
2176}
2177
2179{
2180 Q_Q(QNetworkReplyHttpImpl);
2181
2182 if (outgoingDataBuffer)
2183 uploadByteDevice = QNonContiguousByteDeviceFactory::createShared(outgoingDataBuffer);
2184 else if (outgoingData) {
2185 uploadByteDevice = QNonContiguousByteDeviceFactory::createShared(outgoingData);
2186 } else {
2187 return nullptr;
2188 }
2189
2190 // We want signal emissions only for normal asynchronous uploads
2191 if (!synchronous)
2192 QObject::connect(uploadByteDevice.get(), SIGNAL(readProgress(qint64,qint64)),
2193 q, SLOT(emitReplyUploadProgress(qint64,qint64)));
2194
2195 return uploadByteDevice.get();
2196}
2197
2199{
2200 // This gets called queued, just forward to real call then
2201 finished();
2202}
2203
2205{
2206 Q_Q(QNetworkReplyHttpImpl);
2207 if (transferTimeout)
2208 transferTimeout->stop();
2209 if (state == Finished || state == Aborted)
2210 return;
2211
2212 const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
2213 headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
2214 const qint64 totalSize = totalSizeOpt.value_or(-1);
2215
2216 // if we don't know the total size of or we received everything save the cache.
2217 // If the data is compressed then this is done in readData()
2218 if ((totalSize == -1 || bytesDownloaded == totalSize)
2219 && !decompressHelper.isValid()) {
2221 }
2222
2223 // We check for errorCode too as in case of SSL handshake failure, we still
2224 // get the HTTP redirect status code (301, 303 etc)
2225 if (isHttpRedirectResponse() && errorCode == QNetworkReply::NoError)
2226 return;
2227
2228 state = Finished;
2229 q->setFinished(true);
2230
2231 if (totalSize == -1) {
2232 emit q->downloadProgress(bytesDownloaded, bytesDownloaded);
2233 } else {
2234 emit q->downloadProgress(bytesDownloaded, totalSize);
2235 }
2236
2237 if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
2238 emit q->uploadProgress(0, 0);
2239
2240 emit q->readChannelFinished();
2241 emit q->finished();
2242}
2243
2244void QNetworkReplyHttpImplPrivate::_q_error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
2245{
2246 this->error(code, errorMessage);
2247}
2248
2249
2250void QNetworkReplyHttpImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
2251{
2252 Q_Q(QNetworkReplyHttpImpl);
2253 // Can't set and emit multiple errors.
2254 if (errorCode != QNetworkReply::NoError) {
2255 // But somewhat unavoidable if we have cancelled the request:
2256 if (errorCode != QNetworkReply::OperationCanceledError
2257 && errorCode != QNetworkReply::TimeoutError) {
2258 qWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.");
2259 }
2260 return;
2261 }
2262
2263 errorCode = code;
2264 q->setErrorString(errorMessage);
2265
2266 // note: might not be a good idea, since users could decide to delete us
2267 // which would delete the backend too...
2268 // maybe we should protect the backend
2269 emit q->errorOccurred(code);
2270}
2271
2273{
2274 // FIXME merge this with replyDownloadMetaData(); ?
2275
2276 Q_Q(QNetworkReplyHttpImpl);
2277 // 1. do we have cookies?
2278 // 2. are we allowed to set them?
2279 Q_ASSERT(manager);
2280
2281 const auto cookiesOpt = QNetworkHeadersPrivate::toSetCookieList(
2282 headers().values(QHttpHeaders::WellKnownHeader::SetCookie));
2283 const auto cookies = cookiesOpt.value_or(QList<QNetworkCookie>());
2284 if (!cookies.empty()
2285 && request.attribute(QNetworkRequest::CookieSaveControlAttribute,
2286 QNetworkRequest::Automatic).toInt() == QNetworkRequest::Automatic) {
2287 QNetworkCookieJar *jar = manager->cookieJar();
2288 if (jar) {
2289 jar->setCookiesFromUrl(cookies, url);
2290 }
2291 }
2292 emit q->metaDataChanged();
2293}
2294
2296{
2297 // check if we can save and if we're allowed to
2298 if (!managerPrivate->networkCache
2299 || !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool())
2300 return;
2301 cacheEnabled = true;
2302}
2303
2305{
2306 return (cacheEnabled && managerPrivate->networkCache != nullptr);
2307}
2308
2310{
2311 if (!enable && !cacheEnabled)
2312 return; // nothing to do
2313 if (enable && cacheEnabled)
2314 return; // nothing to do either!
2315
2316 if (enable) {
2317 if (Q_UNLIKELY(bytesDownloaded)) {
2318 qDebug() << "setCachingEnabled: " << bytesDownloaded << " bytesDownloaded";
2319 // refuse to enable in this case
2320 qCritical("QNetworkReplyImpl: backend error: caching was enabled after some bytes had been written");
2321 return;
2322 }
2323
2325 } else {
2326 // someone told us to turn on, then back off?
2327 // ok... but you should make up your mind
2328 qDebug("QNetworkReplyImpl: setCachingEnabled(true) called after setCachingEnabled(false)");
2329 managerPrivate->networkCache->remove(url);
2330 cacheSaveDevice = nullptr;
2331 cacheEnabled = false;
2332 }
2333}
2334
2336{
2337 return operation == QNetworkAccessManager::GetOperation || operation == QNetworkAccessManager::HeadOperation;
2338}
2339
2341{
2342 if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) {
2343 managerPrivate->networkCache->remove(url);
2344 } else if (cacheEnabled && cacheSaveDevice) {
2345 managerPrivate->networkCache->insert(cacheSaveDevice);
2346 }
2347 cacheSaveDevice = nullptr;
2348 cacheEnabled = false;
2349}
2350
2351QT_END_NAMESPACE
2352
2353#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)