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