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