Coverage Report

Created: 2026-10-28 07:09

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