diff -r 000000000000 -r 1918ee327afb src/network/access/qnetworkreplyimpl.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/network/access/qnetworkreplyimpl.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,716 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $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 "qnetworkaccesshttpbackend_p.h" + +#include + +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), + 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; + + if (!backend) { + error(QNetworkReplyImpl::ProtocolUnknownError, + QCoreApplication::translate("QNetworkReply", "Protocol \"%1\" is unknown").arg(url.scheme())); // not really true!; + finished(); + return; + } + + backend->open(); + 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(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); + 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); + } + } +} + + +void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req, + QIODevice *data) +{ + Q_Q(QNetworkReplyImpl); + + outgoingData = data; + request = req; + url = request.url(); + operation = op; + + if (outgoingData && backend) { + // 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 + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); + } + + q->QIODevice::open(QIODevice::ReadOnly); +} + +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 (!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(0, readBufferMaxSize - readBuffer.byteAmount()); +} + +// 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) { + // 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; + } + } + + qint64 bytesWritten = 0; + for (int i = 0; i < data.bufferCount(); i++) { + QByteArray item = data[i]; + + if (cacheSaveDevice) + cacheSaveDevice->write(item.constData(), item.size()); + readBuffer.append(item); + + bytesWritten += item.size(); + } + data.clear(); + + bytesDownloaded += bytesWritten; + lastBytesDownloaded = bytesDownloaded; + + QPointer qq = q; + + QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + 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::finished() +{ + Q_Q(QNetworkReplyImpl); + if (state == Finished || state == Aborted) + return; + + state = Finished; + pendingNotifications.clear(); + + pauseNotificationHandling(); + QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + if (totalSize.isNull() || totalSize == -1) { + emit q->downloadProgress(bytesDownloaded, bytesDownloaded); + } + + if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer)) + emit q->uploadProgress(0, 0); + resumeNotificationHandling(); + + 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); + // do we have cookies? + if (cookedHeaders.contains(QNetworkRequest::SetCookieHeader) && !manager.isNull()) { + QList cookies = + qvariant_cast >(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 &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); + if (d->isCachingEnabled()) + d->networkCache()->remove(url()); + if (d->outgoingDataBuffer) + delete d->outgoingDataBuffer; +} + +void QNetworkReplyImpl::abort() +{ + Q_D(QNetworkReplyImpl); + if (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")); + 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(); +} + +/*! + 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); +} + +#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 &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(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); +} + +QT_END_NAMESPACE + +#include "moc_qnetworkreplyimpl_p.cpp" +