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