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