blob: 8a0a944a54eaff84c00124d85e5acc0c8a859719 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qnetworkreplyimpl_p.h"
#include "qnetworkaccessbackend_p.h"
#include "qnetworkcookie.h"
#include "qabstractnetworkcache.h"
#include "QtCore/qcoreapplication.h"
#include "QtCore/qdatetime.h"
#include "QtNetwork/qsslconfiguration.h"
#include "QtNetwork/qnetworksession.h"
#include "qnetworkaccesshttpbackend_p.h"
#include "qnetworkaccessmanager_p.h"
#include <QtCore/QCoreApplication>
QT_BEGIN_NAMESPACE
inline QNetworkReplyImplPrivate::QNetworkReplyImplPrivate()
: backend(0), outgoingData(0), outgoingDataBuffer(0),
copyDevice(0),
cacheEnabled(false), cacheSaveDevice(0),
notificationHandlingPaused(false),
bytesDownloaded(0), lastBytesDownloaded(-1), bytesUploaded(-1), preMigrationDownloaded(-1),
httpStatusCode(0),
state(Idle)
{
}
void QNetworkReplyImplPrivate::_q_startOperation()
{
// ensure this function is only being called once
if (state == Working) {
qDebug("QNetworkReplyImpl::_q_startOperation was called more than once");
return;
}
state = Working;
// note: if that method is called directly, it cannot happen that the backend is 0,
// because we just checked via a qobject_cast that we got a http backend (see
// QNetworkReplyImplPrivate::setup())
if (!backend) {
error(QNetworkReplyImpl::ProtocolUnknownError,
QCoreApplication::translate("QNetworkReply", "Protocol \"%1\" is unknown").arg(url.scheme())); // not really true!;
finished();
return;
}
#ifndef QT_NO_BEARERMANAGEMENT
if (!backend->start()) { // ### we should call that method even if bearer is not used
// backend failed to start because the session state is not Connected.
// QNetworkAccessManager will call reply->backend->start() again for us when the session
// state changes.
state = WaitingForSession;
QNetworkSession *session = manager->d_func()->networkSession.data();
if (session) {
Q_Q(QNetworkReplyImpl);
QObject::connect(session, SIGNAL(error(QNetworkSession::SessionError)),
q, SLOT(_q_networkSessionFailed()));
if (!session->isOpen())
session->open();
} else {
qWarning("Backend is waiting for QNetworkSession to connect, but there is none!");
}
return;
}
#endif
if (backend && backend->isSynchronous()) {
state = Finished;
} else {
if (state != Finished) {
if (operation == QNetworkAccessManager::GetOperation)
pendingNotifications.append(NotifyDownstreamReadyWrite);
handleNotifications();
}
}
}
void QNetworkReplyImplPrivate::_q_copyReadyRead()
{
Q_Q(QNetworkReplyImpl);
if (state != Working)
return;
if (!copyDevice || !q->isOpen())
return;
forever {
qint64 bytesToRead = nextDownstreamBlockSize();
if (bytesToRead == 0)
// we'll be called again, eventually
break;
bytesToRead = qBound<qint64>(1, bytesToRead, copyDevice->bytesAvailable());
QByteArray byteData;
byteData.resize(bytesToRead);
qint64 bytesActuallyRead = copyDevice->read(byteData.data(), byteData.size());
if (bytesActuallyRead == -1) {
byteData.clear();
backendNotify(NotifyCopyFinished);
break;
}
byteData.resize(bytesActuallyRead);
readBuffer.append(byteData);
if (!copyDevice->isSequential() && copyDevice->atEnd()) {
backendNotify(NotifyCopyFinished);
bytesDownloaded += bytesActuallyRead;
break;
}
bytesDownloaded += bytesActuallyRead;
}
if (bytesDownloaded == lastBytesDownloaded) {
// we didn't read anything
return;
}
lastBytesDownloaded = bytesDownloaded;
QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
if (preMigrationDownloaded != Q_INT64_C(-1))
totalSize = totalSize.toLongLong() + preMigrationDownloaded;
pauseNotificationHandling();
emit q->downloadProgress(bytesDownloaded,
totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
emit q->readyRead();
resumeNotificationHandling();
}
void QNetworkReplyImplPrivate::_q_copyReadChannelFinished()
{
_q_copyReadyRead();
}
void QNetworkReplyImplPrivate::_q_bufferOutgoingDataFinished()
{
Q_Q(QNetworkReplyImpl);
// make sure this is only called once, ever.
//_q_bufferOutgoingData may call it or the readChannelFinished emission
if (state != Buffering)
return;
// disconnect signals
QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
// finally, start the request
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
}
void QNetworkReplyImplPrivate::_q_bufferOutgoingData()
{
Q_Q(QNetworkReplyImpl);
if (!outgoingDataBuffer) {
// first call, create our buffer
outgoingDataBuffer = new QRingBuffer();
QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
}
qint64 bytesBuffered = 0;
qint64 bytesToBuffer = 0;
// read data into our buffer
forever {
bytesToBuffer = outgoingData->bytesAvailable();
// unknown? just try 2 kB, this also ensures we always try to read the EOF
if (bytesToBuffer <= 0)
bytesToBuffer = 2*1024;
char *dst = outgoingDataBuffer->reserve(bytesToBuffer);
bytesBuffered = outgoingData->read(dst, bytesToBuffer);
if (bytesBuffered == -1) {
// EOF has been reached.
outgoingDataBuffer->chop(bytesToBuffer);
_q_bufferOutgoingDataFinished();
break;
} else if (bytesBuffered == 0) {
// nothing read right now, just wait until we get called again
outgoingDataBuffer->chop(bytesToBuffer);
break;
} else {
// don't break, try to read() again
outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered);
}
}
}
#ifndef QT_NO_BEARERMANAGEMENT
void QNetworkReplyImplPrivate::_q_networkSessionConnected()
{
Q_Q(QNetworkReplyImpl);
if (manager.isNull())
return;
QNetworkSession *session = manager->d_func()->networkSession.data();
if (!session)
return;
if (session->state() != QNetworkSession::Connected)
return;
switch (state) {
case QNetworkReplyImplPrivate::Buffering:
case QNetworkReplyImplPrivate::Working:
case QNetworkReplyImplPrivate::Reconnecting:
// Migrate existing downloads to new network connection.
migrateBackend();
break;
case QNetworkReplyImplPrivate::WaitingForSession:
// Start waiting requests.
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
break;
default:
;
}
}
void QNetworkReplyImplPrivate::_q_networkSessionFailed()
{
// Abort waiting and working replies.
if (state == WaitingForSession || state == Working) {
state = Working;
error(QNetworkReplyImpl::UnknownNetworkError,
QCoreApplication::translate("QNetworkReply", "Network session error."));
finished();
}
}
#endif
void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req,
QIODevice *data)
{
Q_Q(QNetworkReplyImpl);
outgoingData = data;
request = req;
url = request.url();
operation = op;
q->QIODevice::open(QIODevice::ReadOnly);
// Internal code that does a HTTP reply for the synchronous Ajax
// in QtWebKit.
QVariant synchronousHttpAttribute = req.attribute(
static_cast<QNetworkRequest::Attribute>(QNetworkRequest::DownloadBufferAttribute + 1));
if (backend && synchronousHttpAttribute.toBool()) {
backend->setSynchronous(true);
if (outgoingData && outgoingData->isSequential()) {
outgoingDataBuffer = new QRingBuffer();
QByteArray data;
do {
data = outgoingData->readAll();
if (data.isEmpty())
break;
outgoingDataBuffer->append(data);
} while (1);
}
}
if (outgoingData && backend && !backend->isSynchronous()) {
// there is data to be uploaded, e.g. HTTP POST.
if (!backend->needsResetableUploadData() || !outgoingData->isSequential()) {
// backend does not need upload buffering or
// fixed size non-sequential
// just start the operation
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
} else {
bool bufferingDisallowed =
req.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute,
false).toBool();
if (bufferingDisallowed) {
// if a valid content-length header for the request was supplied, we can disable buffering
// if not, we will buffer anyway
if (req.header(QNetworkRequest::ContentLengthHeader).isValid()) {
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
} else {
state = Buffering;
QMetaObject::invokeMethod(q, "_q_bufferOutgoingData", Qt::QueuedConnection);
}
} else {
// _q_startOperation will be called when the buffering has finished.
state = Buffering;
QMetaObject::invokeMethod(q, "_q_bufferOutgoingData", Qt::QueuedConnection);
}
}
} else {
// No outgoing data (e.g. HTTP GET request)
// or no backend
// if no backend, _q_startOperation will handle the error of this
// for HTTP, we want to send out the request as fast as possible to the network, without
// invoking methods in a QueuedConnection
#ifndef QT_NO_HTTP
if (qobject_cast<QNetworkAccessHttpBackend *>(backend) || (backend && backend->isSynchronous())) {
_q_startOperation();
} else {
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
}
#else
if (backend && backend->isSynchronous())
_q_startOperation();
else
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
#endif // QT_NO_HTTP
}
}
void QNetworkReplyImplPrivate::backendNotify(InternalNotifications notification)
{
Q_Q(QNetworkReplyImpl);
if (!pendingNotifications.contains(notification))
pendingNotifications.enqueue(notification);
if (pendingNotifications.size() == 1)
QCoreApplication::postEvent(q, new QEvent(QEvent::NetworkReplyUpdated));
}
void QNetworkReplyImplPrivate::handleNotifications()
{
if (notificationHandlingPaused)
return;
NotificationQueue current = pendingNotifications;
pendingNotifications.clear();
if (state != Working)
return;
while (state == Working && !current.isEmpty()) {
InternalNotifications notification = current.dequeue();
switch (notification) {
case NotifyDownstreamReadyWrite:
if (copyDevice)
_q_copyReadyRead();
else
backend->downstreamReadyWrite();
break;
case NotifyCloseDownstreamChannel:
backend->closeDownstreamChannel();
break;
case NotifyCopyFinished: {
QIODevice *dev = copyDevice;
copyDevice = 0;
backend->copyFinished(dev);
break;
}
}
}
}
// Do not handle the notifications while we are emitting downloadProgress
// or readyRead
void QNetworkReplyImplPrivate::pauseNotificationHandling()
{
notificationHandlingPaused = true;
}
// Resume notification handling
void QNetworkReplyImplPrivate::resumeNotificationHandling()
{
Q_Q(QNetworkReplyImpl);
notificationHandlingPaused = false;
if (pendingNotifications.size() >= 1)
QCoreApplication::postEvent(q, new QEvent(QEvent::NetworkReplyUpdated));
}
QAbstractNetworkCache *QNetworkReplyImplPrivate::networkCache() const
{
if (!backend)
return 0;
return backend->networkCache();
}
void QNetworkReplyImplPrivate::createCache()
{
// check if we can save and if we're allowed to
if (!networkCache()
|| !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool()
|| request.attribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::PreferNetwork).toInt()
== QNetworkRequest::AlwaysNetwork)
return;
cacheEnabled = true;
}
bool QNetworkReplyImplPrivate::isCachingEnabled() const
{
return (cacheEnabled && networkCache() != 0);
}
void QNetworkReplyImplPrivate::setCachingEnabled(bool enable)
{
if (!enable && !cacheEnabled)
return; // nothing to do
if (enable && cacheEnabled)
return; // nothing to do either!
if (enable) {
if (bytesDownloaded) {
// refuse to enable in this case
qCritical("QNetworkReplyImpl: backend error: caching was enabled after some bytes had been written");
return;
}
createCache();
} else {
// someone told us to turn on, then back off?
// ok... but you should make up your mind
qDebug("QNetworkReplyImpl: setCachingEnabled(true) called after setCachingEnabled(false) -- "
"backend %s probably needs to be fixed",
backend->metaObject()->className());
networkCache()->remove(url);
cacheSaveDevice = 0;
cacheEnabled = false;
}
}
void QNetworkReplyImplPrivate::completeCacheSave()
{
if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) {
networkCache()->remove(url);
} else if (cacheEnabled && cacheSaveDevice) {
networkCache()->insert(cacheSaveDevice);
}
cacheSaveDevice = 0;
cacheEnabled = false;
}
void QNetworkReplyImplPrivate::emitUploadProgress(qint64 bytesSent, qint64 bytesTotal)
{
Q_Q(QNetworkReplyImpl);
bytesUploaded = bytesSent;
pauseNotificationHandling();
emit q->uploadProgress(bytesSent, bytesTotal);
resumeNotificationHandling();
}
qint64 QNetworkReplyImplPrivate::nextDownstreamBlockSize() const
{
enum { DesiredBufferSize = 32 * 1024 };
if (readBufferMaxSize == 0)
return DesiredBufferSize;
return qMax<qint64>(0, readBufferMaxSize - readBuffer.byteAmount());
}
void QNetworkReplyImplPrivate::initCacheSaveDevice()
{
Q_Q(QNetworkReplyImpl);
// The disk cache does not support partial content, so don't even try to
// save any such content into the cache.
if (q->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 206) {
cacheEnabled = false;
return;
}
// save the meta data
QNetworkCacheMetaData metaData;
metaData.setUrl(url);
metaData = backend->fetchCacheMetaData(metaData);
// save the redirect request also in the cache
QVariant redirectionTarget = q->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (redirectionTarget.isValid()) {
QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
attributes.insert(QNetworkRequest::RedirectionTargetAttribute, redirectionTarget);
metaData.setAttributes(attributes);
}
cacheSaveDevice = networkCache()->prepare(metaData);
if (!cacheSaveDevice || (cacheSaveDevice && !cacheSaveDevice->isOpen())) {
if (cacheSaveDevice && !cacheSaveDevice->isOpen())
qCritical("QNetworkReplyImpl: network cache returned a device that is not open -- "
"class %s probably needs to be fixed",
networkCache()->metaObject()->className());
networkCache()->remove(url);
cacheSaveDevice = 0;
cacheEnabled = false;
}
}
// we received downstream data and send this to the cache
// and to our readBuffer (which in turn gets read by the user of QNetworkReply)
void QNetworkReplyImplPrivate::appendDownstreamData(QByteDataBuffer &data)
{
Q_Q(QNetworkReplyImpl);
if (!q->isOpen())
return;
if (cacheEnabled && !cacheSaveDevice) {
initCacheSaveDevice();
}
qint64 bytesWritten = 0;
for (int i = 0; i < data.bufferCount(); i++) {
QByteArray const &item = data[i];
if (cacheSaveDevice)
cacheSaveDevice->write(item.constData(), item.size());
readBuffer.append(item);
bytesWritten += item.size();
}
data.clear();
bytesDownloaded += bytesWritten;
lastBytesDownloaded = bytesDownloaded;
appendDownstreamDataSignalEmissions();
}
void QNetworkReplyImplPrivate::appendDownstreamDataSignalEmissions()
{
Q_Q(QNetworkReplyImpl);
QPointer<QNetworkReplyImpl> qq = q;
QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
if (preMigrationDownloaded != Q_INT64_C(-1))
totalSize = totalSize.toLongLong() + preMigrationDownloaded;
pauseNotificationHandling();
emit q->downloadProgress(bytesDownloaded,
totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
// important: At the point of this readyRead(), the data parameter list must be empty,
// else implicit sharing will trigger memcpy when the user is reading data!
emit q->readyRead();
// hopefully we haven't been deleted here
if (!qq.isNull()) {
resumeNotificationHandling();
// do we still have room in the buffer?
if (nextDownstreamBlockSize() > 0)
backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
}
}
// this is used when it was fetched from the cache, right?
void QNetworkReplyImplPrivate::appendDownstreamData(QIODevice *data)
{
Q_Q(QNetworkReplyImpl);
if (!q->isOpen())
return;
// read until EOF from data
if (copyDevice) {
qCritical("QNetworkReplyImpl: copy from QIODevice already in progress -- "
"backend probly needs to be fixed");
return;
}
copyDevice = data;
q->connect(copyDevice, SIGNAL(readyRead()), SLOT(_q_copyReadyRead()));
q->connect(copyDevice, SIGNAL(readChannelFinished()), SLOT(_q_copyReadChannelFinished()));
// start the copy:
_q_copyReadyRead();
}
void QNetworkReplyImplPrivate::appendDownstreamData(const QByteArray &data)
{
Q_UNUSED(data)
// TODO implement
// TODO call
qFatal("QNetworkReplyImplPrivate::appendDownstreamData not implemented");
}
void QNetworkReplyImplPrivate::finished()
{
Q_Q(QNetworkReplyImpl);
if (state == Finished || state == Aborted || state == WaitingForSession)
return;
pauseNotificationHandling();
QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
if (preMigrationDownloaded != Q_INT64_C(-1))
totalSize = totalSize.toLongLong() + preMigrationDownloaded;
if (!manager.isNull()) {
#ifndef QT_NO_BEARERMANAGEMENT
QNetworkSession *session = manager->d_func()->networkSession.data();
if (session && session->state() == QNetworkSession::Roaming &&
state == Working && errorCode != QNetworkReply::OperationCanceledError) {
// only content with a known size will fail with a temporary network failure error
if (!totalSize.isNull()) {
if (bytesDownloaded != totalSize) {
if (migrateBackend()) {
// either we are migrating or the request is finished/aborted
if (state == Reconnecting || state == WaitingForSession) {
resumeNotificationHandling();
return; // exit early if we are migrating.
}
} else {
error(QNetworkReply::TemporaryNetworkFailureError,
QNetworkReply::tr("Temporary network failure."));
}
}
}
}
#endif
}
resumeNotificationHandling();
state = Finished;
pendingNotifications.clear();
pauseNotificationHandling();
if (totalSize.isNull() || totalSize == -1) {
emit q->downloadProgress(bytesDownloaded, bytesDownloaded);
}
if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
emit q->uploadProgress(0, 0);
resumeNotificationHandling();
// if we don't know the total size of or we received everything save the cache
if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize)
completeCacheSave();
// note: might not be a good idea, since users could decide to delete us
// which would delete the backend too...
// maybe we should protect the backend
pauseNotificationHandling();
emit q->readChannelFinished();
emit q->finished();
resumeNotificationHandling();
}
void QNetworkReplyImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
{
Q_Q(QNetworkReplyImpl);
errorCode = code;
q->setErrorString(errorMessage);
// note: might not be a good idea, since users could decide to delete us
// which would delete the backend too...
// maybe we should protect the backend
emit q->error(code);
}
void QNetworkReplyImplPrivate::metaDataChanged()
{
Q_Q(QNetworkReplyImpl);
// 1. do we have cookies?
// 2. are we allowed to set them?
if (cookedHeaders.contains(QNetworkRequest::SetCookieHeader) && !manager.isNull()
&& (static_cast<QNetworkRequest::LoadControl>
(request.attribute(QNetworkRequest::CookieSaveControlAttribute,
QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Automatic)) {
QList<QNetworkCookie> cookies =
qvariant_cast<QList<QNetworkCookie> >(cookedHeaders.value(QNetworkRequest::SetCookieHeader));
QNetworkCookieJar *jar = manager->cookieJar();
if (jar)
jar->setCookiesFromUrl(cookies, url);
}
emit q->metaDataChanged();
}
void QNetworkReplyImplPrivate::redirectionRequested(const QUrl &target)
{
attributes.insert(QNetworkRequest::RedirectionTargetAttribute, target);
}
void QNetworkReplyImplPrivate::sslErrors(const QList<QSslError> &errors)
{
#ifndef QT_NO_OPENSSL
Q_Q(QNetworkReplyImpl);
emit q->sslErrors(errors);
#else
Q_UNUSED(errors);
#endif
}
bool QNetworkReplyImplPrivate::isFinished() const
{
return (state == Finished || state == Aborted);
}
QNetworkReplyImpl::QNetworkReplyImpl(QObject *parent)
: QNetworkReply(*new QNetworkReplyImplPrivate, parent)
{
}
QNetworkReplyImpl::~QNetworkReplyImpl()
{
Q_D(QNetworkReplyImpl);
// This code removes the data from the cache if it was prematurely aborted.
// See QNetworkReplyImplPrivate::completeCacheSave(), we disable caching there after the cache
// save had been properly finished. So if it is still enabled it means we got deleted/aborted.
if (d->isCachingEnabled())
d->networkCache()->remove(url());
if (d->outgoingDataBuffer)
delete d->outgoingDataBuffer;
}
void QNetworkReplyImpl::abort()
{
Q_D(QNetworkReplyImpl);
if (d->state == QNetworkReplyImplPrivate::Finished || d->state == QNetworkReplyImplPrivate::Aborted)
return;
// stop both upload and download
if (d->outgoingData)
disconnect(d->outgoingData, 0, this, 0);
if (d->copyDevice)
disconnect(d->copyDevice, 0, this, 0);
QNetworkReply::close();
if (d->state != QNetworkReplyImplPrivate::Finished) {
// emit signals
d->error(OperationCanceledError, tr("Operation canceled"));
if (d->state == QNetworkReplyImplPrivate::WaitingForSession)
d->state = QNetworkReplyImplPrivate::Working;
d->finished();
}
d->state = QNetworkReplyImplPrivate::Aborted;
// finished may access the backend
if (d->backend) {
d->backend->deleteLater();
d->backend = 0;
}
}
void QNetworkReplyImpl::close()
{
Q_D(QNetworkReplyImpl);
if (d->state == QNetworkReplyImplPrivate::Aborted ||
d->state == QNetworkReplyImplPrivate::Finished)
return;
// stop the download
if (d->backend)
d->backend->closeDownstreamChannel();
if (d->copyDevice)
disconnect(d->copyDevice, 0, this, 0);
QNetworkReply::close();
// emit signals
d->error(OperationCanceledError, tr("Operation canceled"));
d->finished();
}
bool QNetworkReplyImpl::canReadLine () const
{
Q_D(const QNetworkReplyImpl);
return QNetworkReply::canReadLine() || d->readBuffer.canReadLine();
}
/*!
Returns the number of bytes available for reading with
QIODevice::read(). The number of bytes available may grow until
the finished() signal is emitted.
*/
qint64 QNetworkReplyImpl::bytesAvailable() const
{
return QNetworkReply::bytesAvailable() + d_func()->readBuffer.byteAmount();
}
void QNetworkReplyImpl::setReadBufferSize(qint64 size)
{
Q_D(QNetworkReplyImpl);
if (size > d->readBufferMaxSize &&
size > d->readBuffer.byteAmount())
d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
QNetworkReply::setReadBufferSize(size);
if (d->backend)
d->backend->setDownstreamLimited(d->readBufferMaxSize > 0);
}
#ifndef QT_NO_OPENSSL
QSslConfiguration QNetworkReplyImpl::sslConfigurationImplementation() const
{
Q_D(const QNetworkReplyImpl);
QSslConfiguration config;
if (d->backend)
d->backend->fetchSslConfiguration(config);
return config;
}
void QNetworkReplyImpl::setSslConfigurationImplementation(const QSslConfiguration &config)
{
Q_D(QNetworkReplyImpl);
if (d->backend && !config.isNull())
d->backend->setSslConfiguration(config);
}
void QNetworkReplyImpl::ignoreSslErrors()
{
Q_D(QNetworkReplyImpl);
if (d->backend)
d->backend->ignoreSslErrors();
}
void QNetworkReplyImpl::ignoreSslErrorsImplementation(const QList<QSslError> &errors)
{
Q_D(QNetworkReplyImpl);
if (d->backend)
d->backend->ignoreSslErrors(errors);
}
#endif // QT_NO_OPENSSL
/*!
\internal
*/
qint64 QNetworkReplyImpl::readData(char *data, qint64 maxlen)
{
Q_D(QNetworkReplyImpl);
if (d->readBuffer.isEmpty())
return d->state == QNetworkReplyImplPrivate::Finished ? -1 : 0;
d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
if (maxlen == 1) {
// optimization for getChar()
*data = d->readBuffer.getChar();
return 1;
}
maxlen = qMin<qint64>(maxlen, d->readBuffer.byteAmount());
return d->readBuffer.read(data, maxlen);
}
/*!
\internal Reimplemented for internal purposes
*/
bool QNetworkReplyImpl::event(QEvent *e)
{
if (e->type() == QEvent::NetworkReplyUpdated) {
d_func()->handleNotifications();
return true;
}
return QObject::event(e);
}
/*
Migrates the backend of the QNetworkReply to a new network connection if required. Returns
true if the reply is migrated or it is not required; otherwise returns false.
*/
bool QNetworkReplyImplPrivate::migrateBackend()
{
Q_Q(QNetworkReplyImpl);
// Network reply is already finished or aborted, don't need to migrate.
if (state == Finished || state == Aborted)
return true;
// Request has outgoing data, not migrating.
if (outgoingData)
return false;
// Request is serviced from the cache, don't need to migrate.
if (copyDevice)
return true;
// Backend does not support resuming download.
if (backend && !backend->canResume())
return false;
state = QNetworkReplyImplPrivate::Reconnecting;
if (backend) {
delete backend;
backend = 0;
}
cookedHeaders.clear();
rawHeaders.clear();
preMigrationDownloaded = bytesDownloaded;
backend = manager->d_func()->findBackend(operation, request);
if (backend) {
backend->setParent(q);
backend->reply = this;
backend->setResumeOffset(bytesDownloaded);
}
#ifndef QT_NO_HTTP
if (qobject_cast<QNetworkAccessHttpBackend *>(backend)) {
_q_startOperation();
} else {
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
}
#else
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
#endif // QT_NO_HTTP
return true;
}
#ifndef QT_NO_BEARERMANAGEMENT
QDisabledNetworkReply::QDisabledNetworkReply(QObject *parent,
const QNetworkRequest &req,
QNetworkAccessManager::Operation op)
: QNetworkReply(parent)
{
setRequest(req);
setUrl(req.url());
setOperation(op);
qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
QString msg = QCoreApplication::translate("QNetworkAccessManager",
"Network access is disabled.");
setError(UnknownNetworkError, msg);
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QNetworkReply::NetworkError, UnknownNetworkError));
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
}
QDisabledNetworkReply::~QDisabledNetworkReply()
{
}
#endif
QT_END_NAMESPACE
#include "moc_qnetworkreplyimpl_p.cpp"