summaryrefslogtreecommitdiffstats
path: root/src/network/socket/qabstractsocket.h
blob: ed187e54eb38698ea9cbd44c26cd8f004c545b40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** 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 either Technology Preview License Agreement or the
** Beta Release License Agreement.
**
** 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.0, 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://www.qtsoftware.com/contact.
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef QABSTRACTSOCKET_H
#define QABSTRACTSOCKET_H

#include <QtCore/qiodevice.h>
#include <QtCore/qobject.h>
#ifndef QT_NO_DEBUG_STREAM
#include <QtCore/qdebug.h>
#endif

QT_BEGIN_HEADER

QT_BEGIN_NAMESPACE

QT_MODULE(Network)

class QHostAddress;
#ifndef QT_NO_NETWORKPROXY
class QNetworkProxy;
#endif
class QAbstractSocketPrivate;
class QAuthenticator;

class Q_NETWORK_EXPORT QAbstractSocket : public QIODevice
{
    Q_OBJECT
public:
    enum SocketType {
        TcpSocket,
        UdpSocket,
        UnknownSocketType = -1
    };
    enum NetworkLayerProtocol {
        IPv4Protocol,
        IPv6Protocol,
        UnknownNetworkLayerProtocol = -1
    };
    enum SocketError {
        ConnectionRefusedError,
        RemoteHostClosedError,
        HostNotFoundError,
        SocketAccessError,
        SocketResourceError,
        SocketTimeoutError,                     /* 5 */
        DatagramTooLargeError,
        NetworkError,
        AddressInUseError,
        SocketAddressNotAvailableError,
        UnsupportedSocketOperationError,        /* 10 */
        UnfinishedSocketOperationError,
        ProxyAuthenticationRequiredError,
        SslHandshakeFailedError,
        ProxyConnectionRefusedError,
        ProxyConnectionClosedError,             /* 15 */
        ProxyConnectionTimeoutError,
        ProxyNotFoundError,
        ProxyProtocolError,

        UnknownSocketError = -1
    };
    enum SocketState {
        UnconnectedState,
        HostLookupState,
        ConnectingState,
        ConnectedState,
        BoundState,
        ListeningState,
        ClosingState
#ifdef QT3_SUPPORT
        ,
        Idle = UnconnectedState,
        HostLookup = HostLookupState,
        Connecting = ConnectingState,
        Connected = ConnectedState,
        Closing = ClosingState,
        Connection = ConnectedState
#endif
    };

    QAbstractSocket(SocketType socketType, QObject *parent);
    virtual ~QAbstractSocket();

    // ### Qt 5: Make connectToHost() and disconnectFromHost() virtual.
    void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite);
    void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite);
    void disconnectFromHost();

    bool isValid() const;

    qint64 bytesAvailable() const;
    qint64 bytesToWrite() const;

    bool canReadLine() const;

    quint16 localPort() const;
    QHostAddress localAddress() const;
    quint16 peerPort() const;
    QHostAddress peerAddress() const;
    QString peerName() const;

    // ### Qt 5: Make setReadBufferSize() virtual
    qint64 readBufferSize() const;
    void setReadBufferSize(qint64 size);

    void abort();

    // ### Qt 5: Make socketDescriptor() and setSocketDescriptor() virtual.
    int socketDescriptor() const;
    bool setSocketDescriptor(int socketDescriptor, SocketState state = ConnectedState,
                             OpenMode openMode = ReadWrite);

    SocketType socketType() const;
    SocketState state() const;
    SocketError error() const;

    // from QIODevice
    void close();
    bool isSequential() const;
    bool atEnd() const;
    bool flush();

    // for synchronous access
    // ### Qt 5: Make waitForConnected() and waitForDisconnected() virtual.
    bool waitForConnected(int msecs = 30000);
    bool waitForReadyRead(int msecs = 30000);
    bool waitForBytesWritten(int msecs = 30000);
    bool waitForDisconnected(int msecs = 30000);

#ifndef QT_NO_NETWORKPROXY
    void setProxy(const QNetworkProxy &networkProxy);
    QNetworkProxy proxy() const;
#endif

Q_SIGNALS:
    void hostFound();
    void connected();
    void disconnected();
    void stateChanged(QAbstractSocket::SocketState);
    void error(QAbstractSocket::SocketError);
#ifndef QT_NO_NETWORKPROXY
    void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator);
#endif

protected Q_SLOTS:
    void connectToHostImplementation(const QString &hostName, quint16 port, OpenMode mode = ReadWrite);
    void disconnectFromHostImplementation();

protected:
    qint64 readData(char *data, qint64 maxlen);
    qint64 readLineData(char *data, qint64 maxlen);
    qint64 writeData(const char *data, qint64 len);

    void setSocketState(SocketState state);
    void setSocketError(SocketError socketError);
    void setLocalPort(quint16 port);
    void setLocalAddress(const QHostAddress &address);
    void setPeerPort(quint16 port);
    void setPeerAddress(const QHostAddress &address);
    void setPeerName(const QString &name);

    QAbstractSocket(SocketType socketType, QAbstractSocketPrivate &dd, QObject *parent = 0);

private:
    Q_DECLARE_PRIVATE(QAbstractSocket)
    Q_DISABLE_COPY(QAbstractSocket)

    Q_PRIVATE_SLOT(d_func(), void _q_connectToNextAddress())
    Q_PRIVATE_SLOT(d_func(), void _q_startConnecting(const QHostInfo &))
    Q_PRIVATE_SLOT(d_func(), void _q_abortConnectionAttempt())
    Q_PRIVATE_SLOT(d_func(), void _q_testConnection())

#ifdef QT3_SUPPORT
public:
    enum Error {
        ErrConnectionRefused = ConnectionRefusedError,
        ErrHostNotFound = HostNotFoundError,
        ErrSocketRead = UnknownSocketError
    };
    inline QT3_SUPPORT int socket() const { return socketDescriptor(); }
    inline QT3_SUPPORT void setSocket(int socket) { setSocketDescriptor(socket); }
    inline QT3_SUPPORT qulonglong waitForMore(int msecs, bool *timeout = 0) const
    {
        QAbstractSocket *that = const_cast<QAbstractSocket *>(this);
        if (that->waitForReadyRead(msecs))
            return qulonglong(bytesAvailable());
        if (error() == SocketTimeoutError && timeout)
            *timeout = true;
        return 0;
    }
    typedef SocketState State;
Q_SIGNALS:
    QT_MOC_COMPAT void connectionClosed(); // same as disconnected()
    QT_MOC_COMPAT void delayedCloseFinished(); // same as disconnected()


#endif
};

#ifndef QT_NO_DEBUG_STREAM
Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketError);
Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketState);
#endif

QT_END_NAMESPACE

QT_END_HEADER

#endif // QABSTRACTSOCKET_H
n class="hl com">** 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 "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; } if (!backend->start()) { // 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()->session; if (session) { if (!session->isOpen()) session->open(); } else { qWarning("Backend is waiting for QNetworkSession to connect, but there is none!"); } return; } 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); } } } void QNetworkReplyImplPrivate::_q_networkSessionOnline() { Q_Q(QNetworkReplyImpl); 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: qDebug() << "How do we handle replies in state" << state; } } 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 // for HTTP, we want to send out the request as fast as possible to the network, without // invoking methods in a QueuedConnection if (qobject_cast<QNetworkAccessHttpBackend *>(backend)) { _q_startOperation(); } else { 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 (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()); } // 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<QNetworkReplyImpl> qq = q; QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); if (totalSize.isNull()) { RawHeadersList::ConstIterator it = findRawHeader("Content-Range"); if (it != rawHeaders.constEnd()) { int index = it->second.lastIndexOf('/'); if (index != -1) totalSize = it->second.mid(index + 1).toLongLong() - preMigrationDownloaded; } else { qDebug() << "Could not find Content-Length or Content-Range header"; } } 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::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; QNetworkSession *session = manager->d_func()->session; 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()) { qDebug() << "Connection broke during download."; qDebug() << "Don't worry, we've already started roaming :)"; if (bytesDownloaded == totalSize) { qDebug() << "Luckily download has already finished."; } else { qDebug() << "Download hasn't finished"; if (migrateBackend()) { // either we are migrating or the request is finished/aborted if (state == Reconnecting) { resumeNotificationHandling(); return; // exit early if we are migrating. } } else { qDebug() << "Could not migrate backend, application needs to send another requeset."; error(QNetworkReply::TemporaryNetworkFailureError, q->tr("Temporary network failure.")); } } } } 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); // do we have cookies? if (cookedHeaders.contains(QNetworkRequest::SetCookieHeader) && !manager.isNull()) { 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); 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")); 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); 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); } bool QNetworkReplyImplPrivate::migrateBackend() { Q_Q(QNetworkReplyImpl); if (state == QNetworkReplyImplPrivate::Finished || state == QNetworkReplyImplPrivate::Aborted) { qDebug() << "Network reply is already finished/aborted."; return true; } if (!qobject_cast<QNetworkAccessHttpBackend *>(backend)) { qDebug() << "Resume only support by http backend, not migrating."; return false; } if (outgoingData) { qDebug() << "Request has outgoing data, not migrating."; return false; } qDebug() << "Need to check for only cacheable content."; // stop both upload and download if (outgoingData) outgoingData->disconnect(q); if (copyDevice) copyDevice->disconnect(q); state = QNetworkReplyImplPrivate::Reconnecting; if (backend) { delete backend; backend = 0; } RawHeadersList::ConstIterator it = findRawHeader("Accept-Ranges"); if (it == rawHeaders.constEnd() || it->second == "none") { qDebug() << "Range header not supported by server/resource."; return false; } cookedHeaders.clear(); rawHeaders.clear(); preMigrationDownloaded = bytesDownloaded; request.setRawHeader("Range", "bytes=" + QByteArray::number(preMigrationDownloaded) + '-'); backend = manager->d_func()->findBackend(operation, request); if (backend) { backend->setParent(q); backend->reply = this; } if (qobject_cast<QNetworkAccessHttpBackend *>(backend)) { _q_startOperation(); } else { QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); } return true; } 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() { } QT_END_NAMESPACE #include "moc_qnetworkreplyimpl_p.cpp"