1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 //#define QHTTPTHREADDELEGATE_DEBUG
43 #include "qhttpthreaddelegate_p.h"
47 #include <QAuthenticator>
50 #include "private/qhttpnetworkreply_p.h"
51 #include "private/qnetworkaccesscache_p.h"
52 #include "private/qnoncontiguousbytedevice_p.h"
58 static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url)
60 QNetworkReply::NetworkError code;
62 switch (httpStatusCode) {
63 case 401: // Authorization required
64 code = QNetworkReply::AuthenticationRequiredError;
67 case 403: // Access denied
68 code = QNetworkReply::ContentOperationNotPermittedError;
71 case 404: // Not Found
72 code = QNetworkReply::ContentNotFoundError;
75 case 405: // Method Not Allowed
76 code = QNetworkReply::ContentOperationNotPermittedError;
80 code = QNetworkReply::ProxyAuthenticationRequiredError;
83 case 418: // I'm a teapot
84 code = QNetworkReply::ProtocolInvalidOperationError;
89 if (httpStatusCode > 500) {
90 // some kind of server error
91 code = QNetworkReply::ProtocolUnknownError;
92 } else if (httpStatusCode >= 400) {
93 // content error we did not handle above
94 code = QNetworkReply::UnknownContentError;
96 qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
97 httpStatusCode, qPrintable(url.toString()));
98 code = QNetworkReply::ProtocolFailure;
106 static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy)
110 bool isEncrypted = copy.scheme().toLower() == QLatin1String("https");
111 copy.setPort(copy.port(isEncrypted ? 443 : 80));
112 result = copy.toString(QUrl::RemoveUserInfo | QUrl::RemovePath |
113 QUrl::RemoveQuery | QUrl::RemoveFragment | QUrl::FullyEncoded);
115 #ifndef QT_NO_NETWORKPROXY
116 if (proxy && proxy->type() != QNetworkProxy::NoProxy) {
119 switch (proxy->type()) {
120 case QNetworkProxy::Socks5Proxy:
121 key.setScheme(QLatin1String("proxy-socks5"));
124 case QNetworkProxy::HttpProxy:
125 case QNetworkProxy::HttpCachingProxy:
126 key.setScheme(QLatin1String("proxy-http"));
133 if (!key.scheme().isEmpty()) {
134 key.setUserName(proxy->user());
135 key.setHost(proxy->hostName());
136 key.setPort(proxy->port());
137 key.setQuery(result);
138 result = key.toString(QUrl::FullyEncoded);
145 return "http-connection:" + result.toLatin1();
148 class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
149 public QNetworkAccessCache::CacheableObject
153 #ifdef QT_NO_BEARERMANAGEMENT
154 QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt)
155 : QHttpNetworkConnection(hostName, port, encrypt)
157 QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt, QSharedPointer<QNetworkSession> networkSession)
158 : QHttpNetworkConnection(hostName, port, encrypt, /*parent=*/0, networkSession)
165 virtual void dispose()
167 #if 0 // sample code; do this right with the API
168 Q_ASSERT(!isWorking());
175 QThreadStorage<QNetworkAccessCache *> QHttpThreadDelegate::connections;
178 QHttpThreadDelegate::~QHttpThreadDelegate()
180 // It could be that the main thread has asked us to shut down, so we need to delete the HTTP reply
185 // Get the object cache that stores our QHttpNetworkConnection objects
186 // and release the entry for this QHttpNetworkConnection
187 if (connections.hasLocalData() && !cacheKey.isEmpty()) {
188 connections.localData()->releaseEntry(cacheKey);
193 QHttpThreadDelegate::QHttpThreadDelegate(QObject *parent) :
196 , downloadBufferMaximumSize(0)
197 , readBufferMaxSize(0)
199 , pendingDownloadData(0)
200 , pendingDownloadProgress(0)
202 , incomingStatusCode(0)
203 , isPipeliningUsed(false)
204 , incomingContentLength(-1)
205 , incomingErrorCode(QNetworkReply::NoError)
209 , synchronousRequestLoop(0)
213 // This is invoked as BlockingQueuedConnection from QNetworkAccessHttpBackend in the user thread
214 void QHttpThreadDelegate::startRequestSynchronously()
216 #ifdef QHTTPTHREADDELEGATE_DEBUG
217 qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId();
221 QEventLoop synchronousRequestLoop;
222 this->synchronousRequestLoop = &synchronousRequestLoop;
224 // Worst case timeout
225 QTimer::singleShot(30*1000, this, SLOT(abortRequest()));
227 QMetaObject::invokeMethod(this, "startRequest", Qt::QueuedConnection);
228 synchronousRequestLoop.exec();
230 connections.localData()->releaseEntry(cacheKey);
231 connections.setLocalData(0);
233 #ifdef QHTTPTHREADDELEGATE_DEBUG
234 qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId() << "finished";
239 // This is invoked as QueuedConnection from QNetworkAccessHttpBackend in the user thread
240 void QHttpThreadDelegate::startRequest()
242 #ifdef QHTTPTHREADDELEGATE_DEBUG
243 qDebug() << "QHttpThreadDelegate::startRequest() thread=" << QThread::currentThreadId();
245 // Check QThreadStorage for the QNetworkAccessCache
246 // If not there, create this connection cache
247 if (!connections.hasLocalData()) {
248 connections.setLocalData(new QNetworkAccessCache());
251 // check if we have an open connection to this host
252 QUrl urlCopy = httpRequest.url();
253 urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
255 #ifndef QT_NO_NETWORKPROXY
256 if (transparentProxy.type() != QNetworkProxy::NoProxy)
257 cacheKey = makeCacheKey(urlCopy, &transparentProxy);
258 else if (cacheProxy.type() != QNetworkProxy::NoProxy)
259 cacheKey = makeCacheKey(urlCopy, &cacheProxy);
262 cacheKey = makeCacheKey(urlCopy, 0);
265 // the http object is actually a QHttpNetworkConnection
266 httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
267 if (httpConnection == 0) {
268 // no entry in cache; create an object
269 // the http object is actually a QHttpNetworkConnection
270 #ifdef QT_NO_BEARERMANAGEMENT
271 httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl);
273 httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl, networkSession);
276 // Set the QSslConfiguration from this QNetworkRequest.
277 if (ssl && incomingSslConfiguration != QSslConfiguration::defaultConfiguration()) {
278 httpConnection->setSslConfiguration(incomingSslConfiguration);
282 #ifndef QT_NO_NETWORKPROXY
283 httpConnection->setTransparentProxy(transparentProxy);
284 httpConnection->setCacheProxy(cacheProxy);
287 // cache the QHttpNetworkConnection corresponding to this cache key
288 connections.localData()->addEntry(cacheKey, httpConnection);
292 // Send the request to the connection
293 httpReply = httpConnection->sendRequest(httpRequest);
294 httpReply->setParent(this);
296 // Connect the reply signals that we need to handle and then forward
298 connect(httpReply,SIGNAL(headerChanged()), this, SLOT(synchronousHeaderChangedSlot()));
299 connect(httpReply,SIGNAL(finished()), this, SLOT(synchronousFinishedSlot()));
300 connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError, const QString)),
301 this, SLOT(synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
303 connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
304 this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
305 connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
306 this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));
308 // Don't care about ignored SSL errors for now in the synchronous HTTP case.
309 } else if (!synchronous) {
310 connect(httpReply,SIGNAL(headerChanged()), this, SLOT(headerChangedSlot()));
311 connect(httpReply,SIGNAL(finished()), this, SLOT(finishedSlot()));
312 connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError, const QString)),
313 this, SLOT(finishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
314 // some signals are only interesting when normal asynchronous style is used
315 connect(httpReply,SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
316 connect(httpReply,SIGNAL(dataReadProgress(qint64, qint64)), this, SLOT(dataReadProgressSlot(qint64,qint64)));
318 connect(httpReply,SIGNAL(sslErrors(const QList<QSslError>)), this, SLOT(sslErrorsSlot(QList<QSslError>)));
321 // In the asynchronous HTTP case we can just forward those signals
322 // Connect the reply signals that we can directly forward
323 connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
324 this, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)));
325 connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
326 this, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
329 connect(httpReply, SIGNAL(cacheCredentials(QHttpNetworkRequest,QAuthenticator*)),
330 this, SLOT(cacheCredentialsSlot(QHttpNetworkRequest,QAuthenticator*)));
333 // This gets called from the user thread or by the synchronous HTTP timeout timer
334 void QHttpThreadDelegate::abortRequest()
336 #ifdef QHTTPTHREADDELEGATE_DEBUG
337 qDebug() << "QHttpThreadDelegate::abortRequest() thread=" << QThread::currentThreadId() << "sync=" << synchronous;
344 // Got aborted by the timeout timer
346 incomingErrorCode = QNetworkReply::TimeoutError;
347 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
349 //only delete this for asynchronous mode or QNetworkAccessHttpBackend will crash - see QNetworkAccessHttpBackend::postRequest()
354 void QHttpThreadDelegate::readBufferSizeChanged(qint64 size)
356 #ifdef QHTTPTHREADDELEGATE_DEBUG
357 qDebug() << "QHttpThreadDelegate::readBufferSizeChanged() size " << size;
360 httpReply->setDownstreamLimited(size > 0);
361 httpReply->setReadBufferSize(size);
362 readBufferMaxSize = size;
366 void QHttpThreadDelegate::readBufferFreed(qint64 size)
368 if (readBufferMaxSize) {
369 bytesEmitted -= size;
371 QMetaObject::invokeMethod(this, "readyReadSlot", Qt::QueuedConnection);
375 void QHttpThreadDelegate::readyReadSlot()
380 // Don't do in zerocopy case
381 if (!downloadBuffer.isNull())
384 if (readBufferMaxSize) {
385 if (bytesEmitted < readBufferMaxSize) {
386 qint64 sizeEmitted = 0;
387 while (httpReply->readAnyAvailable() && (sizeEmitted < (readBufferMaxSize-bytesEmitted))) {
388 if (httpReply->sizeNextBlock() > (readBufferMaxSize-bytesEmitted)) {
389 sizeEmitted = readBufferMaxSize-bytesEmitted;
390 bytesEmitted += sizeEmitted;
391 pendingDownloadData->fetchAndAddRelease(1);
392 emit downloadData(httpReply->read(sizeEmitted));
394 sizeEmitted = httpReply->sizeNextBlock();
395 bytesEmitted += sizeEmitted;
396 pendingDownloadData->fetchAndAddRelease(1);
397 emit downloadData(httpReply->readAny());
401 // We need to wait until we empty data from the read buffer in the reply.
405 while (httpReply->readAnyAvailable()) {
406 pendingDownloadData->fetchAndAddRelease(1);
407 emit downloadData(httpReply->readAny());
412 void QHttpThreadDelegate::finishedSlot()
415 qWarning("QHttpThreadDelegate::finishedSlot: HTTP reply had already been deleted, internal problem. Please report.");
418 #ifdef QHTTPTHREADDELEGATE_DEBUG
419 qDebug() << "QHttpThreadDelegate::finishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode();
422 // If there is still some data left emit that now
423 while (httpReply->readAnyAvailable()) {
424 pendingDownloadData->fetchAndAddRelease(1);
425 emit downloadData(httpReply->readAny());
430 emit sslConfigurationChanged(httpReply->sslConfiguration());
433 if (httpReply->statusCode() >= 400) {
434 // it's an error reply
435 QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
436 "Error downloading %1 - server replied: %2"));
437 msg = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase());
438 emit error(statusCodeFromHttp(httpReply->statusCode(), httpRequest.url()), msg);
441 emit downloadFinished();
443 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
444 QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
448 void QHttpThreadDelegate::synchronousFinishedSlot()
450 #ifdef QHTTPTHREADDELEGATE_DEBUG
451 qDebug() << "QHttpThreadDelegate::synchronousFinishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode();
453 if (httpReply->statusCode() >= 400) {
454 // it's an error reply
455 QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
456 "Error downloading %1 - server replied: %2"));
457 incomingErrorDetail = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase());
458 incomingErrorCode = statusCodeFromHttp(httpReply->statusCode(), httpRequest.url());
461 synchronousDownloadData = httpReply->readAll();
463 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
464 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
468 void QHttpThreadDelegate::finishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail)
471 qWarning("QHttpThreadDelegate::finishedWithErrorSlot: HTTP reply had already been deleted, internal problem. Please report.");
474 #ifdef QHTTPTHREADDELEGATE_DEBUG
475 qDebug() << "QHttpThreadDelegate::finishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail;
480 emit sslConfigurationChanged(httpReply->sslConfiguration());
482 emit error(errorCode,detail);
483 emit downloadFinished();
486 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
487 QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
492 void QHttpThreadDelegate::synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail)
494 #ifdef QHTTPTHREADDELEGATE_DEBUG
495 qDebug() << "QHttpThreadDelegate::synchronousFinishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail;
497 incomingErrorCode = errorCode;
498 incomingErrorDetail = detail;
500 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
501 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
505 static void downloadBufferDeleter(char *ptr)
510 void QHttpThreadDelegate::headerChangedSlot()
512 #ifdef QHTTPTHREADDELEGATE_DEBUG
513 qDebug() << "QHttpThreadDelegate::headerChangedSlot() thread=" << QThread::currentThreadId();
518 emit sslConfigurationChanged(httpReply->sslConfiguration());
521 // Is using a zerocopy buffer allowed by user and possible with this reply?
522 if (httpReply->supportsUserProvidedDownloadBuffer()
523 && (downloadBufferMaximumSize > 0) && (httpReply->contentLength() <= downloadBufferMaximumSize)) {
525 char *buf = new char[httpReply->contentLength()]; // throws if allocation fails
527 downloadBuffer = QSharedPointer<char>(buf, downloadBufferDeleter);
528 httpReply->setUserProvidedDownloadBuffer(buf);
530 } QT_CATCH(const std::bad_alloc &) {
531 // in out of memory situations, don't use downloadbuffer.
535 // We fetch this into our own
536 incomingHeaders = httpReply->header();
537 incomingStatusCode = httpReply->statusCode();
538 incomingReasonPhrase = httpReply->reasonPhrase();
539 isPipeliningUsed = httpReply->isPipeliningUsed();
540 incomingContentLength = httpReply->contentLength();
542 emit downloadMetaData(incomingHeaders,
544 incomingReasonPhrase,
547 incomingContentLength);
550 void QHttpThreadDelegate::synchronousHeaderChangedSlot()
552 #ifdef QHTTPTHREADDELEGATE_DEBUG
553 qDebug() << "QHttpThreadDelegate::synchronousHeaderChangedSlot() thread=" << QThread::currentThreadId();
555 // Store the information we need in this object, the QNetworkAccessHttpBackend will later read it
556 incomingHeaders = httpReply->header();
557 incomingStatusCode = httpReply->statusCode();
558 incomingReasonPhrase = httpReply->reasonPhrase();
559 isPipeliningUsed = httpReply->isPipeliningUsed();
560 incomingContentLength = httpReply->contentLength();
564 void QHttpThreadDelegate::dataReadProgressSlot(qint64 done, qint64 total)
566 // If we don't have a download buffer don't attempt to go this codepath
567 // It is not used by QNetworkAccessHttpBackend
568 if (downloadBuffer.isNull())
571 pendingDownloadProgress->fetchAndAddRelease(1);
572 emit downloadProgress(done, total);
575 void QHttpThreadDelegate::cacheCredentialsSlot(const QHttpNetworkRequest &request, QAuthenticator *authenticator)
577 authenticationManager->cacheCredentials(request.url(), authenticator);
582 void QHttpThreadDelegate::sslErrorsSlot(const QList<QSslError> &errors)
584 emit sslConfigurationChanged(httpReply->sslConfiguration());
586 bool ignoreAll = false;
587 QList<QSslError> specificErrors;
588 emit sslErrors(errors, &ignoreAll, &specificErrors);
590 httpReply->ignoreSslErrors();
591 if (!specificErrors.isEmpty())
592 httpReply->ignoreSslErrors(specificErrors);
596 void QHttpThreadDelegate::synchronousAuthenticationRequiredSlot(const QHttpNetworkRequest &request, QAuthenticator *a)
599 #ifdef QHTTPTHREADDELEGATE_DEBUG
600 qDebug() << "QHttpThreadDelegate::synchronousAuthenticationRequiredSlot() thread=" << QThread::currentThreadId();
603 // Ask the credential cache
604 QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), a);
605 if (!credential.isNull()) {
606 a->setUser(credential.user);
607 a->setPassword(credential.password);
610 // Disconnect this connection now since we only want to ask the authentication cache once.
611 QObject::disconnect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
612 this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
615 #ifndef QT_NO_NETWORKPROXY
616 void QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot(const QNetworkProxy &p, QAuthenticator *a)
618 #ifdef QHTTPTHREADDELEGATE_DEBUG
619 qDebug() << "QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot() thread=" << QThread::currentThreadId();
621 // Ask the credential cache
622 QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedProxyCredentials(p, a);
623 if (!credential.isNull()) {
624 a->setUser(credential.user);
625 a->setPassword(credential.password);
628 // Disconnect this connection now since we only want to ask the authentication cache once.
629 QObject::disconnect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
630 this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));