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 QHTTPTHREADDELEGATE_DEBUG
41 #include "qhttpthreaddelegate_p.h"
42 
43 #include <QThread>
44 #include <QTimer>
45 #include <QAuthenticator>
46 #include <QEventLoop>
47 #include <QCryptographicHash>
48 
49 #include "private/qhttpnetworkreply_p.h"
50 #include "private/qnetworkaccesscache_p.h"
51 #include "private/qnoncontiguousbytedevice_p.h"
52 
53 QT_BEGIN_NAMESPACE
54 
statusCodeFromHttp(int httpStatusCode,const QUrl & url)55 static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url)
56 {
57     QNetworkReply::NetworkError code;
58     // we've got an error
59     switch (httpStatusCode) {
60     case 400:               // Bad Request
61         code = QNetworkReply::ProtocolInvalidOperationError;
62         break;
63 
64     case 401:               // Authorization required
65         code = QNetworkReply::AuthenticationRequiredError;
66         break;
67 
68     case 403:               // Access denied
69         code = QNetworkReply::ContentAccessDenied;
70         break;
71 
72     case 404:               // Not Found
73         code = QNetworkReply::ContentNotFoundError;
74         break;
75 
76     case 405:               // Method Not Allowed
77         code = QNetworkReply::ContentOperationNotPermittedError;
78         break;
79 
80     case 407:
81         code = QNetworkReply::ProxyAuthenticationRequiredError;
82         break;
83 
84     case 409:               // Resource Conflict
85         code = QNetworkReply::ContentConflictError;
86         break;
87 
88     case 410:               // Content no longer available
89         code = QNetworkReply::ContentGoneError;
90         break;
91 
92     case 418:               // I'm a teapot
93         code = QNetworkReply::ProtocolInvalidOperationError;
94         break;
95 
96     case 500:               // Internal Server Error
97         code = QNetworkReply::InternalServerError;
98         break;
99 
100     case 501:               // Server does not support this functionality
101         code = QNetworkReply::OperationNotImplementedError;
102         break;
103 
104     case 503:               // Service unavailable
105         code = QNetworkReply::ServiceUnavailableError;
106         break;
107 
108     default:
109         if (httpStatusCode > 500) {
110             // some kind of server error
111             code = QNetworkReply::UnknownServerError;
112         } else if (httpStatusCode >= 400) {
113             // content error we did not handle above
114             code = QNetworkReply::UnknownContentError;
115         } else {
116             qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
117                      httpStatusCode, qPrintable(url.toString()));
118             code = QNetworkReply::ProtocolFailure;
119         }
120     }
121 
122     return code;
123 }
124 
125 
makeCacheKey(QUrl & url,QNetworkProxy * proxy,const QString & peerVerifyName)126 static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy, const QString &peerVerifyName)
127 {
128     QString result;
129     QUrl copy = url;
130     QString scheme = copy.scheme();
131     bool isEncrypted = scheme == QLatin1String("https")
132                        || scheme == QLatin1String("preconnect-https");
133     copy.setPort(copy.port(isEncrypted ? 443 : 80));
134     if (scheme == QLatin1String("preconnect-http")) {
135         copy.setScheme(QLatin1String("http"));
136     } else if (scheme == QLatin1String("preconnect-https")) {
137         copy.setScheme(QLatin1String("https"));
138     }
139     result = copy.toString(QUrl::RemoveUserInfo | QUrl::RemovePath |
140                            QUrl::RemoveQuery | QUrl::RemoveFragment | QUrl::FullyEncoded);
141 
142 #ifndef QT_NO_NETWORKPROXY
143     if (proxy && proxy->type() != QNetworkProxy::NoProxy) {
144         QUrl key;
145 
146         switch (proxy->type()) {
147         case QNetworkProxy::Socks5Proxy:
148             key.setScheme(QLatin1String("proxy-socks5"));
149             break;
150 
151         case QNetworkProxy::HttpProxy:
152         case QNetworkProxy::HttpCachingProxy:
153             key.setScheme(QLatin1String("proxy-http"));
154             break;
155 
156         default:
157             break;
158         }
159 
160         if (!key.scheme().isEmpty()) {
161             const QByteArray obfuscatedPassword = QCryptographicHash::hash(proxy->password().toUtf8(),
162                                                                            QCryptographicHash::Sha1).toHex();
163             key.setUserName(proxy->user());
164             key.setPassword(QString::fromUtf8(obfuscatedPassword));
165             key.setHost(proxy->hostName());
166             key.setPort(proxy->port());
167             key.setQuery(result);
168             result = key.toString(QUrl::FullyEncoded);
169         }
170     }
171 #else
172     Q_UNUSED(proxy)
173 #endif
174     if (!peerVerifyName.isEmpty())
175         result += QLatin1Char(':') + peerVerifyName;
176     return "http-connection:" + std::move(result).toLatin1();
177 }
178 
179 class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
180                                       public QNetworkAccessCache::CacheableObject
181 {
182     // Q_OBJECT
183 public:
184 #ifdef QT_NO_BEARERMANAGEMENT
QNetworkAccessCachedHttpConnection(const QString & hostName,quint16 port,bool encrypt,QHttpNetworkConnection::ConnectionType connectionType)185     QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt,
186                                        QHttpNetworkConnection::ConnectionType connectionType)
187         : QHttpNetworkConnection(hostName, port, encrypt, connectionType)
188 #else // ### Qt6: Remove section
189     QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt,
190                                        QHttpNetworkConnection::ConnectionType connectionType,
191                                        QSharedPointer<QNetworkSession> networkSession)
192         : QHttpNetworkConnection(hostName, port, encrypt, connectionType, /*parent=*/nullptr,
193                                  std::move(networkSession))
194 #endif
195     {
196         setExpires(true);
197         setShareable(true);
198     }
199 
dispose()200     virtual void dispose() override
201     {
202 #if 0  // sample code; do this right with the API
203         Q_ASSERT(!isWorking());
204 #endif
205         delete this;
206     }
207 };
208 
209 
210 QThreadStorage<QNetworkAccessCache *> QHttpThreadDelegate::connections;
211 
212 
~QHttpThreadDelegate()213 QHttpThreadDelegate::~QHttpThreadDelegate()
214 {
215     // It could be that the main thread has asked us to shut down, so we need to delete the HTTP reply
216     if (httpReply) {
217         delete httpReply;
218     }
219 
220     // Get the object cache that stores our QHttpNetworkConnection objects
221     // and release the entry for this QHttpNetworkConnection
222     if (connections.hasLocalData() && !cacheKey.isEmpty()) {
223         connections.localData()->releaseEntry(cacheKey);
224     }
225 }
226 
227 
QHttpThreadDelegate(QObject * parent)228 QHttpThreadDelegate::QHttpThreadDelegate(QObject *parent) :
229     QObject(parent)
230     , ssl(false)
231     , downloadBufferMaximumSize(0)
232     , readBufferMaxSize(0)
233     , bytesEmitted(0)
234     , pendingDownloadData()
235     , pendingDownloadProgress()
236     , synchronous(false)
237     , incomingStatusCode(0)
238     , isPipeliningUsed(false)
239     , isSpdyUsed(false)
240     , incomingContentLength(-1)
241     , removedContentLength(-1)
242     , incomingErrorCode(QNetworkReply::NoError)
243     , downloadBuffer()
244     , httpConnection(nullptr)
245     , httpReply(nullptr)
246     , synchronousRequestLoop(nullptr)
247 {
248 }
249 
250 // This is invoked as BlockingQueuedConnection from QNetworkAccessHttpBackend in the user thread
startRequestSynchronously()251 void QHttpThreadDelegate::startRequestSynchronously()
252 {
253 #ifdef QHTTPTHREADDELEGATE_DEBUG
254     qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId();
255 #endif
256     synchronous = true;
257 
258     QEventLoop synchronousRequestLoop;
259     this->synchronousRequestLoop = &synchronousRequestLoop;
260 
261     // Worst case timeout
262     QTimer::singleShot(30*1000, this, SLOT(abortRequest()));
263 
264     QMetaObject::invokeMethod(this, "startRequest", Qt::QueuedConnection);
265     synchronousRequestLoop.exec();
266 
267     connections.localData()->releaseEntry(cacheKey);
268     connections.setLocalData(0);
269 
270 #ifdef QHTTPTHREADDELEGATE_DEBUG
271     qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId() << "finished";
272 #endif
273 }
274 
275 
276 // This is invoked as QueuedConnection from QNetworkAccessHttpBackend in the user thread
startRequest()277 void QHttpThreadDelegate::startRequest()
278 {
279 #ifdef QHTTPTHREADDELEGATE_DEBUG
280     qDebug() << "QHttpThreadDelegate::startRequest() thread=" << QThread::currentThreadId();
281 #endif
282     // Check QThreadStorage for the QNetworkAccessCache
283     // If not there, create this connection cache
284     if (!connections.hasLocalData()) {
285         connections.setLocalData(new QNetworkAccessCache());
286     }
287 
288     // check if we have an open connection to this host
289     QUrl urlCopy = httpRequest.url();
290     urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
291 
292     QHttpNetworkConnection::ConnectionType connectionType
293         = httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2
294                                        : QHttpNetworkConnection::ConnectionTypeHTTP;
295     if (httpRequest.isHTTP2Direct()) {
296         Q_ASSERT(!httpRequest.isHTTP2Allowed());
297         connectionType = QHttpNetworkConnection::ConnectionTypeHTTP2Direct;
298     }
299 
300 #if QT_CONFIG(ssl)
301     // See qnetworkreplyhttpimpl, delegate's initialization code.
302     Q_ASSERT(!ssl || incomingSslConfiguration.data());
303 #endif // QT_CONFIG(ssl)
304 
305     const bool isH2 = httpRequest.isHTTP2Allowed() || httpRequest.isHTTP2Direct();
306     if (isH2) {
307 #if QT_CONFIG(ssl)
308         if (ssl) {
309             if (!httpRequest.isHTTP2Direct()) {
310                 QList<QByteArray> protocols;
311                 protocols << QSslConfiguration::ALPNProtocolHTTP2
312                           << QSslConfiguration::NextProtocolHttp1_1;
313                 incomingSslConfiguration->setAllowedNextProtocols(protocols);
314             }
315             urlCopy.setScheme(QStringLiteral("h2s"));
316         } else
317 #endif // QT_CONFIG(ssl)
318         {
319             urlCopy.setScheme(QStringLiteral("h2"));
320         }
321     }
322 
323 #ifndef QT_NO_SSL
324     if (!isH2 && httpRequest.isSPDYAllowed() && ssl) {
325         connectionType = QHttpNetworkConnection::ConnectionTypeSPDY;
326         urlCopy.setScheme(QStringLiteral("spdy")); // to differentiate SPDY requests from HTTPS requests
327         QList<QByteArray> nextProtocols;
328         nextProtocols << QSslConfiguration::NextProtocolSpdy3_0
329                       << QSslConfiguration::NextProtocolHttp1_1;
330         incomingSslConfiguration->setAllowedNextProtocols(nextProtocols);
331     }
332 #endif // QT_NO_SSL
333 
334 #ifndef QT_NO_NETWORKPROXY
335     if (transparentProxy.type() != QNetworkProxy::NoProxy)
336         cacheKey = makeCacheKey(urlCopy, &transparentProxy, httpRequest.peerVerifyName());
337     else if (cacheProxy.type() != QNetworkProxy::NoProxy)
338         cacheKey = makeCacheKey(urlCopy, &cacheProxy, httpRequest.peerVerifyName());
339     else
340 #endif
341         cacheKey = makeCacheKey(urlCopy, nullptr, httpRequest.peerVerifyName());
342 
343     // the http object is actually a QHttpNetworkConnection
344     httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
345     if (!httpConnection) {
346         // no entry in cache; create an object
347         // the http object is actually a QHttpNetworkConnection
348 #ifdef QT_NO_BEARERMANAGEMENT
349         httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
350                                                                 connectionType);
351 #else // ### Qt6: Remove section
352         httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
353                                                                 connectionType,
354                                                                 networkSession);
355 #endif // QT_NO_BEARERMANAGEMENT
356         if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
357             || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
358             httpConnection->setHttp2Parameters(http2Parameters);
359         }
360 #ifndef QT_NO_SSL
361         // Set the QSslConfiguration from this QNetworkRequest.
362         if (ssl)
363             httpConnection->setSslConfiguration(*incomingSslConfiguration);
364 #endif
365 
366 #ifndef QT_NO_NETWORKPROXY
367         httpConnection->setTransparentProxy(transparentProxy);
368         httpConnection->setCacheProxy(cacheProxy);
369 #endif
370         httpConnection->setPeerVerifyName(httpRequest.peerVerifyName());
371         // cache the QHttpNetworkConnection corresponding to this cache key
372         connections.localData()->addEntry(cacheKey, httpConnection);
373     } else {
374         if (httpRequest.withCredentials()) {
375             QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), nullptr);
376             if (!credential.user.isEmpty() && !credential.password.isEmpty()) {
377                 QAuthenticator auth;
378                 auth.setUser(credential.user);
379                 auth.setPassword(credential.password);
380                 httpConnection->d_func()->copyCredentials(-1, &auth, false);
381             }
382         }
383     }
384 
385     // Send the request to the connection
386     httpReply = httpConnection->sendRequest(httpRequest);
387     httpReply->setParent(this);
388 
389     // Connect the reply signals that we need to handle and then forward
390     if (synchronous) {
391         connect(httpReply,SIGNAL(headerChanged()), this, SLOT(synchronousHeaderChangedSlot()));
392         connect(httpReply,SIGNAL(finished()), this, SLOT(synchronousFinishedSlot()));
393         connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
394                 this, SLOT(synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
395 
396         connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
397                 this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
398 #ifndef QT_NO_NETWORKPROXY
399         connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
400                 this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));
401 #endif
402 
403         // Don't care about ignored SSL errors for now in the synchronous HTTP case.
404     } else if (!synchronous) {
405         connect(httpReply,SIGNAL(headerChanged()), this, SLOT(headerChangedSlot()));
406         connect(httpReply,SIGNAL(finished()), this, SLOT(finishedSlot()));
407         connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
408                 this, SLOT(finishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
409         // some signals are only interesting when normal asynchronous style is used
410         connect(httpReply,SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
411         connect(httpReply,SIGNAL(dataReadProgress(qint64,qint64)), this, SLOT(dataReadProgressSlot(qint64,qint64)));
412 #ifndef QT_NO_SSL
413         connect(httpReply,SIGNAL(encrypted()), this, SLOT(encryptedSlot()));
414         connect(httpReply,SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrorsSlot(QList<QSslError>)));
415         connect(httpReply,SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
416                 this, SLOT(preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator*)));
417 #endif
418 
419         // In the asynchronous HTTP case we can just forward those signals
420         // Connect the reply signals that we can directly forward
421         connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
422                 this, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)));
423 #ifndef QT_NO_NETWORKPROXY
424         connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
425                 this, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
426 #endif
427     }
428 
429     connect(httpReply, SIGNAL(cacheCredentials(QHttpNetworkRequest,QAuthenticator*)),
430             this, SLOT(cacheCredentialsSlot(QHttpNetworkRequest,QAuthenticator*)));
431     if (httpReply->errorCode() != QNetworkReply::NoError) {
432         if (synchronous)
433             synchronousFinishedWithErrorSlot(httpReply->errorCode(), httpReply->errorString());
434         else
435             finishedWithErrorSlot(httpReply->errorCode(), httpReply->errorString());
436     }
437 }
438 
439 // This gets called from the user thread or by the synchronous HTTP timeout timer
abortRequest()440 void QHttpThreadDelegate::abortRequest()
441 {
442 #ifdef QHTTPTHREADDELEGATE_DEBUG
443     qDebug() << "QHttpThreadDelegate::abortRequest() thread=" << QThread::currentThreadId() << "sync=" << synchronous;
444 #endif
445     if (httpReply) {
446         httpReply->abort();
447         delete httpReply;
448         httpReply = nullptr;
449     }
450 
451     // Got aborted by the timeout timer
452     if (synchronous) {
453         incomingErrorCode = QNetworkReply::TimeoutError;
454         QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
455     } else {
456         //only delete this for asynchronous mode or QNetworkAccessHttpBackend will crash - see QNetworkAccessHttpBackend::postRequest()
457         this->deleteLater();
458     }
459 }
460 
readBufferSizeChanged(qint64 size)461 void QHttpThreadDelegate::readBufferSizeChanged(qint64 size)
462 {
463 #ifdef QHTTPTHREADDELEGATE_DEBUG
464     qDebug() << "QHttpThreadDelegate::readBufferSizeChanged() size " << size;
465 #endif
466     if (httpReply) {
467         httpReply->setDownstreamLimited(size > 0);
468         httpReply->setReadBufferSize(size);
469         readBufferMaxSize = size;
470     }
471 }
472 
readBufferFreed(qint64 size)473 void QHttpThreadDelegate::readBufferFreed(qint64 size)
474 {
475     if (readBufferMaxSize) {
476         bytesEmitted -= size;
477 
478         QMetaObject::invokeMethod(this, "readyReadSlot", Qt::QueuedConnection);
479     }
480 }
481 
readyReadSlot()482 void QHttpThreadDelegate::readyReadSlot()
483 {
484     if (!httpReply)
485         return;
486 
487     // Don't do in zerocopy case
488     if (!downloadBuffer.isNull())
489         return;
490 
491     if (readBufferMaxSize) {
492         if (bytesEmitted < readBufferMaxSize) {
493             qint64 sizeEmitted = 0;
494             while (httpReply->readAnyAvailable() && (sizeEmitted < (readBufferMaxSize-bytesEmitted))) {
495                 if (httpReply->sizeNextBlock() > (readBufferMaxSize-bytesEmitted)) {
496                     sizeEmitted = readBufferMaxSize-bytesEmitted;
497                     bytesEmitted += sizeEmitted;
498                     pendingDownloadData->fetchAndAddRelease(1);
499                     emit downloadData(httpReply->read(sizeEmitted));
500                 } else {
501                     sizeEmitted = httpReply->sizeNextBlock();
502                     bytesEmitted += sizeEmitted;
503                     pendingDownloadData->fetchAndAddRelease(1);
504                     emit downloadData(httpReply->readAny());
505                 }
506             }
507         } else {
508             // We need to wait until we empty data from the read buffer in the reply.
509         }
510 
511     } else {
512         while (httpReply->readAnyAvailable()) {
513             pendingDownloadData->fetchAndAddRelease(1);
514             emit downloadData(httpReply->readAny());
515         }
516     }
517 }
518 
finishedSlot()519 void QHttpThreadDelegate::finishedSlot()
520 {
521     if (!httpReply)
522         return;
523 
524 #ifdef QHTTPTHREADDELEGATE_DEBUG
525     qDebug() << "QHttpThreadDelegate::finishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode();
526 #endif
527 
528     // If there is still some data left emit that now
529     while (httpReply->readAnyAvailable()) {
530         pendingDownloadData->fetchAndAddRelease(1);
531         emit downloadData(httpReply->readAny());
532     }
533 
534 #ifndef QT_NO_SSL
535     if (ssl)
536         emit sslConfigurationChanged(httpReply->sslConfiguration());
537 #endif
538 
539     if (httpReply->statusCode() >= 400) {
540             // it's an error reply
541             QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
542                                                           "Error transferring %1 - server replied: %2"));
543             msg = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase());
544             emit error(statusCodeFromHttp(httpReply->statusCode(), httpRequest.url()), msg);
545         }
546 
547     if (httpRequest.isFollowRedirects() && httpReply->isRedirecting())
548         emit redirected(httpReply->redirectUrl(), httpReply->statusCode(), httpReply->request().redirectCount() - 1);
549 
550     emit downloadFinished();
551 
552     QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
553     QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
554     httpReply = nullptr;
555 }
556 
synchronousFinishedSlot()557 void QHttpThreadDelegate::synchronousFinishedSlot()
558 {
559     if (!httpReply)
560         return;
561 
562 #ifdef QHTTPTHREADDELEGATE_DEBUG
563     qDebug() << "QHttpThreadDelegate::synchronousFinishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode();
564 #endif
565     if (httpReply->statusCode() >= 400) {
566             // it's an error reply
567             QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
568                                                           "Error transferring %1 - server replied: %2"));
569             incomingErrorDetail = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase());
570             incomingErrorCode = statusCodeFromHttp(httpReply->statusCode(), httpRequest.url());
571     }
572 
573     synchronousDownloadData = httpReply->readAll();
574 
575     QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
576     QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
577     httpReply = nullptr;
578 }
579 
finishedWithErrorSlot(QNetworkReply::NetworkError errorCode,const QString & detail)580 void QHttpThreadDelegate::finishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail)
581 {
582     if (!httpReply)
583         return;
584 
585 #ifdef QHTTPTHREADDELEGATE_DEBUG
586     qDebug() << "QHttpThreadDelegate::finishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail;
587 #endif
588 
589 #ifndef QT_NO_SSL
590     if (ssl)
591         emit sslConfigurationChanged(httpReply->sslConfiguration());
592 #endif
593     emit error(errorCode,detail);
594     emit downloadFinished();
595 
596 
597     QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
598     QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
599     httpReply = nullptr;
600 }
601 
602 
synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError errorCode,const QString & detail)603 void QHttpThreadDelegate::synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail)
604 {
605     if (!httpReply)
606         return;
607 
608 #ifdef QHTTPTHREADDELEGATE_DEBUG
609     qDebug() << "QHttpThreadDelegate::synchronousFinishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail;
610 #endif
611     incomingErrorCode = errorCode;
612     incomingErrorDetail = detail;
613 
614     synchronousDownloadData = httpReply->readAll();
615 
616     QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
617     QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
618     httpReply = nullptr;
619 }
620 
downloadBufferDeleter(char * ptr)621 static void downloadBufferDeleter(char *ptr)
622 {
623     delete[] ptr;
624 }
625 
headerChangedSlot()626 void QHttpThreadDelegate::headerChangedSlot()
627 {
628     if (!httpReply)
629         return;
630 
631 #ifdef QHTTPTHREADDELEGATE_DEBUG
632     qDebug() << "QHttpThreadDelegate::headerChangedSlot() thread=" << QThread::currentThreadId();
633 #endif
634 
635 #ifndef QT_NO_SSL
636     if (ssl)
637         emit sslConfigurationChanged(httpReply->sslConfiguration());
638 #endif
639 
640     // Is using a zerocopy buffer allowed by user and possible with this reply?
641     if (httpReply->supportsUserProvidedDownloadBuffer()
642         && (downloadBufferMaximumSize > 0) && (httpReply->contentLength() <= downloadBufferMaximumSize)) {
643         QT_TRY {
644             char *buf = new char[httpReply->contentLength()]; // throws if allocation fails
645             if (buf) {
646                 downloadBuffer = QSharedPointer<char>(buf, downloadBufferDeleter);
647                 httpReply->setUserProvidedDownloadBuffer(buf);
648             }
649         } QT_CATCH(const std::bad_alloc &) {
650             // in out of memory situations, don't use downloadbuffer.
651         }
652     }
653 
654     // We fetch this into our own
655     incomingHeaders = httpReply->header();
656     incomingStatusCode = httpReply->statusCode();
657     incomingReasonPhrase = httpReply->reasonPhrase();
658     isPipeliningUsed = httpReply->isPipeliningUsed();
659     incomingContentLength = httpReply->contentLength();
660     removedContentLength = httpReply->removedContentLength();
661     isSpdyUsed = httpReply->isSpdyUsed();
662 
663     emit downloadMetaData(incomingHeaders,
664                           incomingStatusCode,
665                           incomingReasonPhrase,
666                           isPipeliningUsed,
667                           downloadBuffer,
668                           incomingContentLength,
669                           removedContentLength,
670                           isSpdyUsed);
671 }
672 
synchronousHeaderChangedSlot()673 void QHttpThreadDelegate::synchronousHeaderChangedSlot()
674 {
675     if (!httpReply)
676         return;
677 
678 #ifdef QHTTPTHREADDELEGATE_DEBUG
679     qDebug() << "QHttpThreadDelegate::synchronousHeaderChangedSlot() thread=" << QThread::currentThreadId();
680 #endif
681     // Store the information we need in this object, the QNetworkAccessHttpBackend will later read it
682     incomingHeaders = httpReply->header();
683     incomingStatusCode = httpReply->statusCode();
684     incomingReasonPhrase = httpReply->reasonPhrase();
685     isPipeliningUsed = httpReply->isPipeliningUsed();
686     isSpdyUsed = httpReply->isSpdyUsed();
687     incomingContentLength = httpReply->contentLength();
688 }
689 
690 
dataReadProgressSlot(qint64 done,qint64 total)691 void QHttpThreadDelegate::dataReadProgressSlot(qint64 done, qint64 total)
692 {
693     // If we don't have a download buffer don't attempt to go this codepath
694     // It is not used by QNetworkAccessHttpBackend
695     if (downloadBuffer.isNull())
696         return;
697 
698     pendingDownloadProgress->fetchAndAddRelease(1);
699     emit downloadProgress(done, total);
700 }
701 
cacheCredentialsSlot(const QHttpNetworkRequest & request,QAuthenticator * authenticator)702 void QHttpThreadDelegate::cacheCredentialsSlot(const QHttpNetworkRequest &request, QAuthenticator *authenticator)
703 {
704     authenticationManager->cacheCredentials(request.url(), authenticator);
705 }
706 
707 
708 #ifndef QT_NO_SSL
encryptedSlot()709 void QHttpThreadDelegate::encryptedSlot()
710 {
711     if (!httpReply)
712         return;
713 
714     emit sslConfigurationChanged(httpReply->sslConfiguration());
715     emit encrypted();
716 }
717 
sslErrorsSlot(const QList<QSslError> & errors)718 void QHttpThreadDelegate::sslErrorsSlot(const QList<QSslError> &errors)
719 {
720     if (!httpReply)
721         return;
722 
723     emit sslConfigurationChanged(httpReply->sslConfiguration());
724 
725     bool ignoreAll = false;
726     QList<QSslError> specificErrors;
727     emit sslErrors(errors, &ignoreAll, &specificErrors);
728     if (ignoreAll)
729         httpReply->ignoreSslErrors();
730     if (!specificErrors.isEmpty())
731         httpReply->ignoreSslErrors(specificErrors);
732 }
733 
preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator * authenticator)734 void QHttpThreadDelegate::preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator *authenticator)
735 {
736     if (!httpReply)
737         return;
738 
739     emit preSharedKeyAuthenticationRequired(authenticator);
740 }
741 #endif
742 
synchronousAuthenticationRequiredSlot(const QHttpNetworkRequest & request,QAuthenticator * a)743 void QHttpThreadDelegate::synchronousAuthenticationRequiredSlot(const QHttpNetworkRequest &request, QAuthenticator *a)
744 {
745     if (!httpReply)
746         return;
747 
748     Q_UNUSED(request);
749 #ifdef QHTTPTHREADDELEGATE_DEBUG
750     qDebug() << "QHttpThreadDelegate::synchronousAuthenticationRequiredSlot() thread=" << QThread::currentThreadId();
751 #endif
752 
753     // Ask the credential cache
754     QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), a);
755     if (!credential.isNull()) {
756         a->setUser(credential.user);
757         a->setPassword(credential.password);
758     }
759 
760     // Disconnect this connection now since we only want to ask the authentication cache once.
761     QObject::disconnect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
762         this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
763 }
764 
765 #ifndef QT_NO_NETWORKPROXY
synchronousProxyAuthenticationRequiredSlot(const QNetworkProxy & p,QAuthenticator * a)766 void  QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot(const QNetworkProxy &p, QAuthenticator *a)
767 {
768     if (!httpReply)
769         return;
770 
771 #ifdef QHTTPTHREADDELEGATE_DEBUG
772     qDebug() << "QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot() thread=" << QThread::currentThreadId();
773 #endif
774     // Ask the credential cache
775     QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedProxyCredentials(p, a);
776     if (!credential.isNull()) {
777         a->setUser(credential.user);
778         a->setPassword(credential.password);
779     }
780 
781 #ifndef QT_NO_NETWORKPROXY
782     // Disconnect this connection now since we only want to ask the authentication cache once.
783     QObject::disconnect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
784         this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));
785 #endif
786 }
787 
788 #endif
789 
790 QT_END_NAMESPACE
791