summaryrefslogtreecommitdiffstats
path: root/src/network/access/qhttpnetworkconnection.cpp
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@nokia.com>2009-03-23 09:18:55 (GMT)
committerSimon Hausmann <simon.hausmann@nokia.com>2009-03-23 09:18:55 (GMT)
commite5fcad302d86d316390c6b0f62759a067313e8a9 (patch)
treec2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/network/access/qhttpnetworkconnection.cpp
downloadQt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip
Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz
Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2
Long live Qt 4.5!
Diffstat (limited to 'src/network/access/qhttpnetworkconnection.cpp')
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp2464
1 files changed, 2464 insertions, 0 deletions
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
new file mode 100644
index 0000000..edb2988
--- /dev/null
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -0,0 +1,2464 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qhttpnetworkconnection_p.h"
+#include <private/qnetworkrequest_p.h>
+#include <private/qobject_p.h>
+#include <private/qauthenticator_p.h>
+#include <qnetworkproxy.h>
+#include <qauthenticator.h>
+#include <qbytearraymatcher.h>
+#include <qbuffer.h>
+#include <qpair.h>
+#include <qhttp.h>
+#include <qdebug.h>
+
+#ifndef QT_NO_HTTP
+
+#ifndef QT_NO_OPENSSL
+# include <QtNetwork/qsslkey.h>
+# include <QtNetwork/qsslcipher.h>
+# include <QtNetwork/qsslconfiguration.h>
+#endif
+
+#ifndef QT_NO_COMPRESS
+# include <zlib.h>
+static const unsigned char gz_magic[2] = {0x1f, 0x8b}; // gzip magic header
+// gzip flag byte
+#define HEAD_CRC 0x02 // bit 1 set: header CRC present
+#define EXTRA_FIELD 0x04 // bit 2 set: extra field present
+#define ORIG_NAME 0x08 // bit 3 set: original file name present
+#define COMMENT 0x10 // bit 4 set: file comment present
+#define RESERVED 0xE0 // bits 5..7: reserved
+#define CHUNK 16384
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QHttpNetworkHeaderPrivate : public QSharedData
+{
+public:
+ QUrl url;
+ QList<QPair<QByteArray, QByteArray> > fields;
+
+ QHttpNetworkHeaderPrivate(const QUrl &newUrl = QUrl());
+ QHttpNetworkHeaderPrivate(const QHttpNetworkHeaderPrivate &other);
+ inline qint64 contentLength() const;
+ inline void setContentLength(qint64 length);
+
+ inline QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const;
+ inline QList<QByteArray> headerFieldValues(const QByteArray &name) const;
+ inline void setHeaderField(const QByteArray &name, const QByteArray &data);
+ bool operator==(const QHttpNetworkHeaderPrivate &other) const;
+
+};
+
+QHttpNetworkHeaderPrivate::QHttpNetworkHeaderPrivate(const QUrl &newUrl)
+ :url(newUrl)
+{
+}
+
+QHttpNetworkHeaderPrivate::QHttpNetworkHeaderPrivate(const QHttpNetworkHeaderPrivate &other)
+ :QSharedData(other)
+{
+ url = other.url;
+ fields = other.fields;
+}
+
+qint64 QHttpNetworkHeaderPrivate::contentLength() const
+{
+ bool ok = false;
+ QByteArray value = headerField("content-length");
+ qint64 length = value.toULongLong(&ok);
+ if (ok)
+ return length;
+ return -1; // the header field is not set
+}
+
+void QHttpNetworkHeaderPrivate::setContentLength(qint64 length)
+{
+ setHeaderField("Content-Length", QByteArray::number(length));
+}
+
+QByteArray QHttpNetworkHeaderPrivate::headerField(const QByteArray &name, const QByteArray &defaultValue) const
+{
+ QList<QByteArray> allValues = headerFieldValues(name);
+ if (allValues.isEmpty())
+ return defaultValue;
+
+ QByteArray result;
+ bool first = true;
+ foreach (QByteArray value, allValues) {
+ if (!first)
+ result += ", ";
+ first = false;
+ result += value;
+ }
+ return result;
+}
+
+QList<QByteArray> QHttpNetworkHeaderPrivate::headerFieldValues(const QByteArray &name) const
+{
+ QList<QByteArray> result;
+ QByteArray lowerName = name.toLower();
+ QList<QPair<QByteArray, QByteArray> >::ConstIterator it = fields.constBegin(),
+ end = fields.constEnd();
+ for ( ; it != end; ++it)
+ if (lowerName == it->first.toLower())
+ result += it->second;
+
+ return result;
+}
+
+void QHttpNetworkHeaderPrivate::setHeaderField(const QByteArray &name, const QByteArray &data)
+{
+ QByteArray lowerName = name.toLower();
+ QList<QPair<QByteArray, QByteArray> >::Iterator it = fields.begin();
+ while (it != fields.end()) {
+ if (lowerName == it->first.toLower())
+ it = fields.erase(it);
+ else
+ ++it;
+ }
+ fields.append(qMakePair(name, data));
+}
+
+bool QHttpNetworkHeaderPrivate::operator==(const QHttpNetworkHeaderPrivate &other) const
+{
+ return (url == other.url);
+}
+
+// QHttpNetworkRequestPrivate
+class QHttpNetworkRequestPrivate : public QHttpNetworkHeaderPrivate
+{
+public:
+ QHttpNetworkRequestPrivate(QHttpNetworkRequest::Operation op,
+ QHttpNetworkRequest::Priority pri, const QUrl &newUrl = QUrl());
+ QHttpNetworkRequestPrivate(const QHttpNetworkRequestPrivate &other);
+ ~QHttpNetworkRequestPrivate();
+ bool operator==(const QHttpNetworkRequestPrivate &other) const;
+ QByteArray methodName() const;
+ QByteArray uri(bool throughProxy) const;
+
+ static QByteArray header(const QHttpNetworkRequest &request, bool throughProxy);
+
+ QHttpNetworkRequest::Operation operation;
+ QHttpNetworkRequest::Priority priority;
+ mutable QIODevice *data;
+ bool autoDecompress;
+};
+
+QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Operation op,
+ QHttpNetworkRequest::Priority pri, const QUrl &newUrl)
+ : QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), data(0),
+ autoDecompress(false)
+{
+}
+
+QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequestPrivate &other)
+ : QHttpNetworkHeaderPrivate(other)
+{
+ operation = other.operation;
+ priority = other.priority;
+ data = other.data;
+ autoDecompress = other.autoDecompress;
+}
+
+QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate()
+{
+}
+
+bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &other) const
+{
+ return QHttpNetworkHeaderPrivate::operator==(other)
+ && (operation == other.operation)
+ && (data == other.data);
+}
+
+QByteArray QHttpNetworkRequestPrivate::methodName() const
+{
+ QByteArray ba;
+ switch (operation) {
+ case QHttpNetworkRequest::Options:
+ ba += "OPTIONS";
+ break;
+ case QHttpNetworkRequest::Get:
+ ba += "GET";
+ break;
+ case QHttpNetworkRequest::Head:
+ ba += "HEAD";
+ break;
+ case QHttpNetworkRequest::Post:
+ ba += "POST";
+ break;
+ case QHttpNetworkRequest::Put:
+ ba += "PUT";
+ break;
+ case QHttpNetworkRequest::Delete:
+ ba += "DELETE";
+ break;
+ case QHttpNetworkRequest::Trace:
+ ba += "TRACE";
+ break;
+ case QHttpNetworkRequest::Connect:
+ ba += "CONNECT";
+ break;
+ default:
+ break;
+ }
+ return ba;
+}
+
+QByteArray QHttpNetworkRequestPrivate::uri(bool throughProxy) const
+{
+ QUrl::FormattingOptions format(QUrl::RemoveFragment);
+
+ // for POST, query data is send as content
+ if (operation == QHttpNetworkRequest::Post && !data)
+ format |= QUrl::RemoveQuery;
+ // for requests through proxy, the Request-URI contains full url
+ if (throughProxy)
+ format |= QUrl::RemoveUserInfo;
+ else
+ format |= QUrl::RemoveScheme | QUrl::RemoveAuthority;
+ QByteArray uri = url.toEncoded(format);
+ if (uri.isEmpty() || (throughProxy && url.path().isEmpty()))
+ uri += '/';
+ return uri;
+}
+
+QByteArray QHttpNetworkRequestPrivate::header(const QHttpNetworkRequest &request, bool throughProxy)
+{
+ QByteArray ba = request.d->methodName();
+ QByteArray uri = request.d->uri(throughProxy);
+ ba += " " + uri;
+
+ QString majorVersion = QString::number(request.majorVersion());
+ QString minorVersion = QString::number(request.minorVersion());
+ ba += " HTTP/" + majorVersion.toLatin1() + "." + minorVersion.toLatin1() + "\r\n";
+
+ QList<QPair<QByteArray, QByteArray> > fields = request.header();
+ QList<QPair<QByteArray, QByteArray> >::const_iterator it = fields.constBegin();
+ for (; it != fields.constEnd(); ++it)
+ ba += it->first + ": " + it->second + "\r\n";
+ if (request.d->operation == QHttpNetworkRequest::Post) {
+ // add content type, if not set in the request
+ if (request.headerField("content-type").isEmpty())
+ ba += "Content-Type: application/x-www-form-urlencoded\r\n";
+ if (!request.d->data && request.d->url.hasQuery()) {
+ QByteArray query = request.d->url.encodedQuery();
+ ba += "Content-Length: "+ QByteArray::number(query.size()) + "\r\n";
+ ba += "\r\n";
+ ba += query;
+ } else {
+ ba += "\r\n";
+ }
+ } else {
+ ba += "\r\n";
+ }
+ return ba;
+}
+
+class QHttpNetworkReplyPrivate : public QObjectPrivate, public QHttpNetworkHeaderPrivate
+{
+public:
+ QHttpNetworkReplyPrivate(const QUrl &newUrl = QUrl());
+ ~QHttpNetworkReplyPrivate();
+ qint64 readStatus(QAbstractSocket *socket);
+ void parseStatus(const QByteArray &status);
+ qint64 readHeader(QAbstractSocket *socket);
+ void parseHeader(const QByteArray &header);
+ qint64 readBody(QAbstractSocket *socket, QIODevice *out);
+ bool findChallenge(bool forProxy, QByteArray &challenge) const;
+ QAuthenticatorPrivate::Method authenticationMethod(bool isProxy) const;
+ void clear();
+
+ qint64 transferRaw(QIODevice *in, QIODevice *out, qint64 size);
+ qint64 transferChunked(QIODevice *in, QIODevice *out);
+ qint64 getChunkSize(QIODevice *in, qint64 *chunkSize);
+
+ qint64 bytesAvailable() const;
+ bool isChunked();
+ bool connectionCloseEnabled();
+ bool isGzipped();
+#ifndef QT_NO_COMPRESS
+ bool gzipCheckHeader(QByteArray &content, int &pos);
+ int gunzipBodyPartially(QByteArray &compressed, QByteArray &inflated);
+#endif
+ void removeAutoDecompressHeader();
+
+ enum ReplyState {
+ NothingDoneState,
+ ReadingStatusState,
+ ReadingHeaderState,
+ ReadingDataState,
+ AllDoneState
+ } state;
+
+ QHttpNetworkRequest request;
+ int statusCode;
+ int majorVersion;
+ int minorVersion;
+ QString errorString;
+ QString reasonPhrase;
+ qint64 bodyLength;
+ qint64 contentRead;
+ qint64 totalProgress;
+ QByteArray fragment;
+ qint64 currentChunkSize;
+ qint64 currentChunkRead;
+ QPointer<QHttpNetworkConnection> connection;
+ bool initInflate;
+ bool streamEnd;
+#ifndef QT_NO_COMPRESS
+ z_stream inflateStrm;
+#endif
+ bool autoDecompress;
+
+ QByteArray responseData; // uncompressed body
+ QByteArray compressedData; // compressed body (temporary)
+ QBuffer requestDataBuffer;
+ bool requestIsBuffering;
+ bool requestIsPrepared;
+};
+
+QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl)
+ : QHttpNetworkHeaderPrivate(newUrl), state(NothingDoneState), statusCode(100),
+ majorVersion(0), minorVersion(0), bodyLength(0), contentRead(0), totalProgress(0),
+ currentChunkSize(0), currentChunkRead(0), connection(0), initInflate(false),
+ autoDecompress(false), requestIsBuffering(false), requestIsPrepared(false)
+{
+}
+
+QHttpNetworkReplyPrivate::~QHttpNetworkReplyPrivate()
+{
+}
+
+void QHttpNetworkReplyPrivate::clear()
+{
+ state = NothingDoneState;
+ statusCode = 100;
+ bodyLength = 0;
+ contentRead = 0;
+ totalProgress = 0;
+ currentChunkSize = 0;
+ currentChunkRead = 0;
+ connection = 0;
+#ifndef QT_NO_COMPRESS
+ if (initInflate)
+ inflateEnd(&inflateStrm);
+#endif
+ initInflate = false;
+ streamEnd = false;
+ autoDecompress = false;
+ fields.clear();
+}
+
+// QHttpNetworkReplyPrivate
+qint64 QHttpNetworkReplyPrivate::bytesAvailable() const
+{
+ return (state != ReadingDataState ? 0 : fragment.size());
+}
+
+bool QHttpNetworkReplyPrivate::isGzipped()
+{
+ QByteArray encoding = headerField("content-encoding");
+ return encoding.toLower() == "gzip";
+}
+
+void QHttpNetworkReplyPrivate::removeAutoDecompressHeader()
+{
+ // The header "Content-Encoding = gzip" is retained.
+ // Content-Length is removed since the actual one send by the server is for compressed data
+ QByteArray name("content-length");
+ QByteArray lowerName = name.toLower();
+ QList<QPair<QByteArray, QByteArray> >::Iterator it = fields.begin(),
+ end = fields.end();
+ while (it != end) {
+ if (name == it->first.toLower()) {
+ fields.erase(it);
+ break;
+ }
+ ++it;
+ }
+
+}
+
+bool QHttpNetworkReplyPrivate::findChallenge(bool forProxy, QByteArray &challenge) const
+{
+ challenge.clear();
+ // find out the type of authentication protocol requested.
+ QByteArray header = forProxy ? "proxy-authenticate" : "www-authenticate";
+ // pick the best protocol (has to match parsing in QAuthenticatorPrivate)
+ QList<QByteArray> challenges = headerFieldValues(header);
+ for (int i = 0; i<challenges.size(); i++) {
+ QByteArray line = challenges.at(i);
+ if (!line.toLower().startsWith("negotiate"))
+ challenge = line;
+ }
+ return !challenge.isEmpty();
+}
+
+QAuthenticatorPrivate::Method QHttpNetworkReplyPrivate::authenticationMethod(bool isProxy) const
+{
+ // The logic is same as the one used in void QAuthenticatorPrivate::parseHttpResponse()
+ QAuthenticatorPrivate::Method method = QAuthenticatorPrivate::None;
+ QByteArray header = isProxy ? "proxy-authenticate" : "www-authenticate";
+ QList<QByteArray> challenges = headerFieldValues(header);
+ for (int i = 0; i<challenges.size(); i++) {
+ QByteArray line = challenges.at(i).trimmed().toLower();
+ if (method < QAuthenticatorPrivate::Basic
+ && line.startsWith("basic")) {
+ method = QAuthenticatorPrivate::Basic;
+ } else if (method < QAuthenticatorPrivate::Ntlm
+ && line.startsWith("ntlm")) {
+ method = QAuthenticatorPrivate::Ntlm;
+ } else if (method < QAuthenticatorPrivate::DigestMd5
+ && line.startsWith("digest")) {
+ method = QAuthenticatorPrivate::DigestMd5;
+ }
+ }
+ return method;
+}
+
+#ifndef QT_NO_COMPRESS
+bool QHttpNetworkReplyPrivate::gzipCheckHeader(QByteArray &content, int &pos)
+{
+ int method = 0; // method byte
+ int flags = 0; // flags byte
+ bool ret = false;
+
+ // Assure two bytes in the buffer so we can peek ahead -- handle case
+ // where first byte of header is at the end of the buffer after the last
+ // gzip segment
+ pos = -1;
+ QByteArray &body = content;
+ int maxPos = body.size()-1;
+ if (maxPos < 1) {
+ return ret;
+ }
+
+ // Peek ahead to check the gzip magic header
+ if (body[0] != char(gz_magic[0]) ||
+ body[1] != char(gz_magic[1])) {
+ return ret;
+ }
+ pos += 2;
+ // Check the rest of the gzip header
+ if (++pos <= maxPos)
+ method = body[pos];
+ if (pos++ <= maxPos)
+ flags = body[pos];
+ if (method != Z_DEFLATED || (flags & RESERVED) != 0) {
+ return ret;
+ }
+
+ // Discard time, xflags and OS code:
+ pos += 6;
+ if (pos > maxPos)
+ return ret;
+ if ((flags & EXTRA_FIELD) && ((pos+2) <= maxPos)) { // skip the extra field
+ unsigned len = (unsigned)body[++pos];
+ len += ((unsigned)body[++pos])<<8;
+ pos += len;
+ if (pos > maxPos)
+ return ret;
+ }
+ if ((flags & ORIG_NAME) != 0) { // skip the original file name
+ while(++pos <= maxPos && body[pos]) {}
+ }
+ if ((flags & COMMENT) != 0) { // skip the .gz file comment
+ while(++pos <= maxPos && body[pos]) {}
+ }
+ if ((flags & HEAD_CRC) != 0) { // skip the header crc
+ pos += 2;
+ if (pos > maxPos)
+ return ret;
+ }
+ ret = (pos < maxPos); // return failed, if no more bytes left
+ return ret;
+}
+
+int QHttpNetworkReplyPrivate::gunzipBodyPartially(QByteArray &compressed, QByteArray &inflated)
+{
+ int ret = Z_DATA_ERROR;
+ unsigned have;
+ unsigned char out[CHUNK];
+ int pos = -1;
+
+ if (!initInflate) {
+ // check the header
+ if (!gzipCheckHeader(compressed, pos))
+ return ret;
+ // allocate inflate state
+ inflateStrm.zalloc = Z_NULL;
+ inflateStrm.zfree = Z_NULL;
+ inflateStrm.opaque = Z_NULL;
+ inflateStrm.avail_in = 0;
+ inflateStrm.next_in = Z_NULL;
+ ret = inflateInit2(&inflateStrm, -MAX_WBITS);
+ if (ret != Z_OK)
+ return ret;
+ initInflate = true;
+ streamEnd = false;
+ }
+
+ //remove the header.
+ compressed.remove(0, pos+1);
+ // expand until deflate stream ends
+ inflateStrm.next_in = (unsigned char *)compressed.data();
+ inflateStrm.avail_in = compressed.size();
+ do {
+ inflateStrm.avail_out = sizeof(out);
+ inflateStrm.next_out = out;
+ ret = inflate(&inflateStrm, Z_NO_FLUSH);
+ switch (ret) {
+ case Z_NEED_DICT:
+ ret = Z_DATA_ERROR;
+ // and fall through
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ inflateEnd(&inflateStrm);
+ initInflate = false;
+ return ret;
+ }
+ have = sizeof(out) - inflateStrm.avail_out;
+ inflated.append(QByteArray((const char *)out, have));
+ } while (inflateStrm.avail_out == 0);
+ // clean up and return
+ if (ret <= Z_ERRNO || ret == Z_STREAM_END) {
+ inflateEnd(&inflateStrm);
+ initInflate = false;
+ }
+ streamEnd = (ret == Z_STREAM_END);
+ return ret;
+}
+#endif
+
+qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket)
+{
+ qint64 bytes = 0;
+ char c;
+
+ while (socket->bytesAvailable()) {
+ // allow both CRLF & LF (only) line endings
+ if (socket->peek(&c, 1) == 1 && c == '\n') {
+ bytes += socket->read(&c, 1); // read the "n"
+ // remove the CR at the end
+ if (fragment.endsWith('\r')) {
+ fragment.truncate(fragment.length()-1);
+ }
+ parseStatus(fragment);
+ state = ReadingHeaderState;
+ fragment.clear(); // next fragment
+ break;
+ } else {
+ c = 0;
+ bytes += socket->read(&c, 1);
+ fragment.append(c);
+ }
+ }
+ return bytes;
+}
+
+void QHttpNetworkReplyPrivate::parseStatus(const QByteArray &status)
+{
+ const QByteArrayMatcher sp(" ");
+ int i = sp.indexIn(status);
+ const QByteArray version = status.mid(0, i);
+ int j = sp.indexIn(status, i + 1);
+ const QByteArray code = status.mid(i + 1, j - i - 1);
+ const QByteArray reason = status.mid(j + 1, status.count() - j);
+
+ const QByteArrayMatcher slash("/");
+ int k = slash.indexIn(version);
+ const QByteArrayMatcher dot(".");
+ int l = dot.indexIn(version, k);
+ const QByteArray major = version.mid(k + 1, l - k - 1);
+ const QByteArray minor = version.mid(l + 1, version.count() - l);
+
+ majorVersion = QString::fromAscii(major.constData()).toInt();
+ minorVersion = QString::fromAscii(minor.constData()).toInt();
+ statusCode = QString::fromAscii(code.constData()).toInt();
+ reasonPhrase = QString::fromAscii(reason.constData());
+}
+
+qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket)
+{
+ qint64 bytes = 0;
+ char crlfcrlf[5];
+ crlfcrlf[4] = '\0';
+ char c = 0;
+ bool allHeaders = false;
+ while (!allHeaders && socket->bytesAvailable()) {
+ if (socket->peek(&c, 1) == 1 && c == '\n') {
+ // check for possible header endings. As per HTTP rfc,
+ // the header endings will be marked by CRLFCRLF. But
+ // we will allow CRLFLF, LFLF & CRLFCRLF
+ if (fragment.endsWith("\n\r") || fragment.endsWith('\n'))
+ allHeaders = true;
+ }
+ bytes += socket->read(&c, 1);
+ fragment.append(c);
+ }
+ // we received all headers now parse them
+ if (allHeaders) {
+ parseHeader(fragment);
+ state = ReadingDataState;
+ fragment.clear(); // next fragment
+ bodyLength = contentLength(); // cache the length
+ }
+ return bytes;
+}
+
+void QHttpNetworkReplyPrivate::parseHeader(const QByteArray &header)
+{
+ // see rfc2616, sec 4 for information about HTTP/1.1 headers.
+ // allows relaxed parsing here, accepts both CRLF & LF line endings
+ const QByteArrayMatcher lf("\n");
+ const QByteArrayMatcher colon(":");
+ int i = 0;
+ while (i < header.count()) {
+ int j = colon.indexIn(header, i); // field-name
+ if (j == -1)
+ break;
+ const QByteArray field = header.mid(i, j - i).trimmed();
+ j++;
+ // any number of LWS is allowed before and after the value
+ QByteArray value;
+ do {
+ i = lf.indexIn(header, j);
+ if (i == -1)
+ break;
+ if (!value.isEmpty())
+ value += ' ';
+ // check if we have CRLF or only LF
+ bool hasCR = (i && header[i-1] == '\r');
+ int length = i -(hasCR ? 1: 0) - j;
+ value += header.mid(j, length).trimmed();
+ j = ++i;
+ } while (i < header.count() && (header.at(i) == ' ' || header.at(i) == '\t'));
+ if (i == -1)
+ break; // something is wrong
+
+ fields.append(qMakePair(field, value));
+ }
+}
+
+bool QHttpNetworkReplyPrivate::isChunked()
+{
+ return headerField("transfer-encoding").toLower().contains("chunked");
+}
+
+bool QHttpNetworkReplyPrivate::connectionCloseEnabled()
+{
+ return (headerField("connection").toLower().contains("close") ||
+ headerField("proxy-connection").toLower().contains("close"));
+}
+
+qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QIODevice *out)
+{
+ qint64 bytes = 0;
+ if (isChunked()) {
+ bytes += transferChunked(socket, out); // chunked transfer encoding (rfc 2616, sec 3.6)
+ } else if (bodyLength > 0) { // we have a Content-Length
+ bytes += transferRaw(socket, out, bodyLength - contentRead);
+ if (contentRead + bytes == bodyLength)
+ state = AllDoneState;
+ } else {
+ bytes += transferRaw(socket, out, socket->bytesAvailable());
+ }
+ if (state == AllDoneState)
+ socket->readAll(); // Read the rest to clean (CRLF)
+ contentRead += bytes;
+ return bytes;
+}
+
+qint64 QHttpNetworkReplyPrivate::transferRaw(QIODevice *in, QIODevice *out, qint64 size)
+{
+ qint64 bytes = 0;
+ Q_ASSERT(in);
+ Q_ASSERT(out);
+
+ int toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, in->bytesAvailable()));
+ QByteArray raw(toBeRead, 0);
+ while (size > 0) {
+ qint64 read = in->read(raw.data(), raw.size());
+ if (read == 0)
+ return bytes;
+ // ### error checking here
+ qint64 written = out->write(raw.data(), read);
+ if (written == 0)
+ return bytes;
+ if (read != written)
+ qDebug() << "### read" << read << "written" << written;
+ bytes += read;
+ size -= read;
+ out->waitForBytesWritten(-1); // throttle
+ }
+ return bytes;
+
+}
+
+qint64 QHttpNetworkReplyPrivate::transferChunked(QIODevice *in, QIODevice *out)
+{
+ qint64 bytes = 0;
+ while (in->bytesAvailable()) { // while we can read from input
+ // if we are done with the current chunk, get the size of the new chunk
+ if (currentChunkRead >= currentChunkSize) {
+ currentChunkSize = 0;
+ currentChunkRead = 0;
+ if (bytes) {
+ char crlf[2];
+ bytes += in->read(crlf, 2); // read the "\r\n" after the chunk
+ }
+ bytes += getChunkSize(in, &currentChunkSize);
+ if (currentChunkSize == -1)
+ break;
+ }
+ // if the chunk size is 0, end of the stream
+ if (currentChunkSize == 0) {
+ state = AllDoneState;
+ break;
+ }
+ // otherwise, read data
+ qint64 readSize = qMin(in->bytesAvailable(), currentChunkSize - currentChunkRead);
+ QByteArray buffer(readSize, 0);
+ qint64 read = in->read(buffer.data(), readSize);
+ bytes += read;
+ currentChunkRead += read;
+ qint64 written = out->write(buffer);
+ Q_UNUSED(written); // Avoid compile warning when building release
+ Q_ASSERT(read == written);
+ // ### error checking here
+ out->waitForBytesWritten(-1);
+ }
+ return bytes;
+}
+
+qint64 QHttpNetworkReplyPrivate::getChunkSize(QIODevice *in, qint64 *chunkSize)
+{
+ qint64 bytes = 0;
+ char crlf[2];
+ *chunkSize = -1;
+ int bytesAvailable = in->bytesAvailable();
+ while (bytesAvailable > bytes) {
+ qint64 sniffedBytes = in->peek(crlf, 2);
+ int fragmentSize = fragment.size();
+ // check the next two bytes for a "\r\n", skip blank lines
+ if ((fragmentSize && sniffedBytes == 2 && crlf[0] == '\r' && crlf[1] == '\n')
+ ||(fragmentSize > 1 && fragment.endsWith('\r') && crlf[0] == '\n'))
+ {
+ bytes += in->read(crlf, 1); // read the \r or \n
+ if (crlf[0] == '\r')
+ bytes += in->read(crlf, 1); // read the \n
+ bool ok = false;
+ // ignore the chunk-extension
+ fragment = fragment.mid(0, fragment.indexOf(';')).trimmed();
+ *chunkSize = fragment.toLong(&ok, 16);
+ fragment.clear();
+ break; // size done
+ } else {
+ // read the fragment to the buffer
+ char c = 0;
+ bytes += in->read(&c, 1);
+ fragment.append(c);
+ }
+ }
+ return bytes;
+}
+
+// QHttpNetworkConnectionPrivate
+
+typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> HttpMessagePair;
+
+class QHttpNetworkConnectionPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QHttpNetworkConnection)
+public:
+ QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt);
+ ~QHttpNetworkConnectionPrivate();
+ void init();
+ void connectSignals(QAbstractSocket *socket);
+
+ enum SocketState {
+ IdleState = 0, // ready to send request
+ ConnectingState = 1, // connecting to host
+ WritingState = 2, // writing the data
+ WaitingState = 4, // waiting for reply
+ ReadingState = 8, // reading the reply
+ Wait4AuthState = 0x10, // blocked for send till the current authentication slot is done
+ BusyState = (ConnectingState|WritingState|WaitingState|ReadingState|Wait4AuthState)
+ };
+
+ enum { ChunkSize = 4096 };
+
+ int indexOf(QAbstractSocket *socket) const;
+ bool isSocketBusy(QAbstractSocket *socket) const;
+ bool isSocketWriting(QAbstractSocket *socket) const;
+ bool isSocketWaiting(QAbstractSocket *socket) const;
+ bool isSocketReading(QAbstractSocket *socket) const;
+
+ QHttpNetworkReply *queueRequest(const QHttpNetworkRequest &request);
+ void unqueueRequest(QAbstractSocket *socket);
+ void prepareRequest(HttpMessagePair &request);
+ bool sendRequest(QAbstractSocket *socket);
+ void receiveReply(QAbstractSocket *socket, QHttpNetworkReply *reply);
+ void resendCurrentRequest(QAbstractSocket *socket);
+ void closeChannel(int channel);
+ void copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy);
+
+ // private slots
+ void _q_bytesWritten(qint64 bytes); // proceed sending
+ void _q_readyRead(); // pending data to read
+ void _q_disconnected(); // disconnected from host
+ void _q_startNextRequest(); // send the next request from the queue
+ void _q_restartPendingRequest(); // send the currently blocked request
+ void _q_connected(); // start sending request
+ void _q_error(QAbstractSocket::SocketError); // error from socket
+#ifndef QT_NO_NETWORKPROXY
+ void _q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *auth); // from transparent proxy
+#endif
+ void _q_dataReadyReadNoBuffer();
+ void _q_dataReadyReadBuffer();
+
+ void createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request);
+ bool ensureConnection(QAbstractSocket *socket);
+ QString errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket *socket);
+ void eraseData(QHttpNetworkReply *reply);
+#ifndef QT_NO_COMPRESS
+ bool expand(QAbstractSocket *socket, QHttpNetworkReply *reply, bool dataComplete);
+#endif
+ void bufferData(HttpMessagePair &request);
+ void removeReply(QHttpNetworkReply *reply);
+
+ QString hostName;
+ quint16 port;
+ bool encrypt;
+
+ struct Channel {
+ QAbstractSocket *socket;
+ SocketState state;
+ QHttpNetworkRequest request; // current request
+ QHttpNetworkReply *reply; // current reply for this request
+ qint64 written;
+ qint64 bytesTotal;
+ bool resendCurrent;
+ int lastStatus; // last status received on this channel
+ bool pendingEncrypt; // for https (send after encrypted)
+ int reconnectAttempts; // maximum 2 reconnection attempts
+ QAuthenticatorPrivate::Method authMehtod;
+ QAuthenticatorPrivate::Method proxyAuthMehtod;
+ QAuthenticator authenticator;
+ QAuthenticator proxyAuthenticator;
+#ifndef QT_NO_OPENSSL
+ bool ignoreSSLErrors;
+#endif
+ Channel() :state(IdleState), reply(0), written(0), bytesTotal(0), resendCurrent(false), reconnectAttempts(2),
+ authMehtod(QAuthenticatorPrivate::None), proxyAuthMehtod(QAuthenticatorPrivate::None)
+#ifndef QT_NO_OPENSSL
+ , ignoreSSLErrors(false)
+#endif
+ {}
+ };
+ static const int channelCount;
+ Channel channels[2]; // maximum of 2 socket connections to the server
+ bool pendingAuthSignal; // there is an incomplete authentication signal
+ bool pendingProxyAuthSignal; // there is an incomplete proxy authentication signal
+
+ void appendData(QHttpNetworkReply &reply, const QByteArray &fragment, bool compressed);
+ qint64 bytesAvailable(const QHttpNetworkReply &reply, bool compressed = false) const;
+ qint64 read(QHttpNetworkReply &reply, QByteArray &data, qint64 maxSize, bool compressed);
+ void emitReplyError(QAbstractSocket *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode);
+ bool handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend);
+ void allDone(QAbstractSocket *socket, QHttpNetworkReply *reply);
+ void handleStatus(QAbstractSocket *socket, QHttpNetworkReply *reply);
+ inline bool emitSignals(QHttpNetworkReply *reply);
+ inline bool expectContent(QHttpNetworkReply *reply);
+
+#ifndef QT_NO_OPENSSL
+ void _q_encrypted(); // start sending request (https)
+ void _q_sslErrors(const QList<QSslError> &errors); // ssl errors from the socket
+ QSslConfiguration sslConfiguration(const QHttpNetworkReply &reply) const;
+#endif
+
+#ifndef QT_NO_NETWORKPROXY
+ QNetworkProxy networkProxy;
+#endif
+
+ //The request queues
+ QList<HttpMessagePair> highPriorityQueue;
+ QList<HttpMessagePair> lowPriorityQueue;
+};
+
+const int QHttpNetworkConnectionPrivate::channelCount = 2;
+
+QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt)
+: hostName(hostName), port(port), encrypt(encrypt),
+ pendingAuthSignal(false), pendingProxyAuthSignal(false)
+#ifndef QT_NO_NETWORKPROXY
+ , networkProxy(QNetworkProxy::NoProxy)
+#endif
+{
+}
+
+QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate()
+{
+ for (int i = 0; i < channelCount; ++i) {
+ channels[i].socket->close();
+ delete channels[i].socket;
+ }
+}
+
+void QHttpNetworkConnectionPrivate::connectSignals(QAbstractSocket *socket)
+{
+ Q_Q(QHttpNetworkConnection);
+
+ QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
+ q, SLOT(_q_bytesWritten(qint64)),
+ Qt::DirectConnection);
+ QObject::connect(socket, SIGNAL(connected()),
+ q, SLOT(_q_connected()),
+ Qt::DirectConnection);
+ QObject::connect(socket, SIGNAL(readyRead()),
+ q, SLOT(_q_readyRead()),
+ Qt::DirectConnection);
+ QObject::connect(socket, SIGNAL(disconnected()),
+ q, SLOT(_q_disconnected()),
+ Qt::DirectConnection);
+ QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ q, SLOT(_q_error(QAbstractSocket::SocketError)),
+ Qt::DirectConnection);
+#ifndef QT_NO_NETWORKPROXY
+ QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)),
+ q, SLOT(_q_proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)),
+ Qt::DirectConnection);
+#endif
+
+#ifndef QT_NO_OPENSSL
+ QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
+ QObject::connect(sslSocket, SIGNAL(encrypted()),
+ q, SLOT(_q_encrypted()),
+ Qt::DirectConnection);
+ QObject::connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError>&)),
+ q, SLOT(_q_sslErrors(const QList<QSslError>&)),
+ Qt::DirectConnection);
+#endif
+}
+
+void QHttpNetworkConnectionPrivate::init()
+{
+ for (int i = 0; i < channelCount; ++i) {
+#ifndef QT_NO_OPENSSL
+ channels[i].socket = new QSslSocket;
+#else
+ channels[i].socket = new QTcpSocket;
+#endif
+ connectSignals(channels[i].socket);
+ }
+}
+
+int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const
+{
+ for (int i = 0; i < channelCount; ++i)
+ if (channels[i].socket == socket)
+ return i;
+ return -1;
+}
+
+bool QHttpNetworkConnectionPrivate::isSocketBusy(QAbstractSocket *socket) const
+{
+ int i = indexOf(socket);
+ return (channels[i].state & BusyState);
+}
+
+bool QHttpNetworkConnectionPrivate::isSocketWriting(QAbstractSocket *socket) const
+{
+ int i = indexOf(socket);
+ return (i != -1 && (channels[i].state & WritingState));
+}
+
+bool QHttpNetworkConnectionPrivate::isSocketWaiting(QAbstractSocket *socket) const
+{
+ int i = indexOf(socket);
+ return (i != -1 && (channels[i].state & WaitingState));
+}
+
+bool QHttpNetworkConnectionPrivate::isSocketReading(QAbstractSocket *socket) const
+{
+ int i = indexOf(socket);
+ return (i != -1 && (channels[i].state & ReadingState));
+}
+
+
+void QHttpNetworkConnectionPrivate::appendData(QHttpNetworkReply &reply, const QByteArray &fragment, bool compressed)
+{
+ QByteArray *ba = (compressed) ? &reply.d_func()->compressedData : &reply.d_func()->responseData;
+ ba->append(fragment);
+ return;
+}
+
+qint64 QHttpNetworkConnectionPrivate::bytesAvailable(const QHttpNetworkReply &reply, bool compressed) const
+{
+ const QByteArray *ba = (compressed) ? &reply.d_func()->compressedData : &reply.d_func()->responseData;
+ return ba->size();
+}
+
+qint64 QHttpNetworkConnectionPrivate::read(QHttpNetworkReply &reply, QByteArray &data, qint64 maxSize, bool compressed)
+{
+ QByteArray *ba = (compressed) ? &reply.d_func()->compressedData : &reply.d_func()->responseData;
+ if (maxSize == -1 || maxSize >= ba->size()) {
+ // read the whole data
+ data = *ba;
+ ba->clear();
+ } else {
+ // read only the requested length
+ data = ba->mid(0, maxSize);
+ ba->remove(0, maxSize);
+ }
+ return data.size();
+}
+
+void QHttpNetworkConnectionPrivate::eraseData(QHttpNetworkReply *reply)
+{
+ reply->d_func()->compressedData.clear();
+ reply->d_func()->responseData.clear();
+}
+
+void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
+{
+ QHttpNetworkRequest &request = messagePair.first;
+ QHttpNetworkReply *reply = messagePair.second;
+
+ // add missing fields for the request
+ QByteArray value;
+ // check if Content-Length is provided
+ QIODevice *data = request.data();
+ if (data && request.contentLength() == -1) {
+ if (!data->isSequential())
+ request.setContentLength(data->size());
+ else
+ bufferData(messagePair); // ### or do chunked upload
+ }
+ // set the Connection/Proxy-Connection: Keep-Alive headers
+#ifndef QT_NO_NETWORKPROXY
+ if (networkProxy.type() == QNetworkProxy::HttpCachingProxy) {
+ value = request.headerField("proxy-connection");
+ if (value.isEmpty())
+ request.setHeaderField("Proxy-Connection", "Keep-Alive");
+ } else {
+#endif
+ value = request.headerField("connection");
+ if (value.isEmpty())
+ request.setHeaderField("Connection", "Keep-Alive");
+#ifndef QT_NO_NETWORKPROXY
+ }
+#endif
+ // set the gzip header
+ value = request.headerField("accept-encoding");
+ if (value.isEmpty()) {
+#ifndef QT_NO_COMPRESS
+ request.setHeaderField("Accept-Encoding", "gzip");
+ request.d->autoDecompress = true;
+#else
+ // if zlib is not available set this to false always
+ request.d->autoDecompress = false;
+#endif
+ }
+ // set the User Agent
+ value = request.headerField("user-agent");
+ if (value.isEmpty())
+ request.setHeaderField("User-Agent", "Mozilla/5.0");
+ // set the host
+ value = request.headerField("host");
+ if (value.isEmpty()) {
+ QByteArray host = QUrl::toAce(hostName);
+
+ int port = request.url().port();
+ if (port != -1) {
+ host += ':';
+ host += QByteArray::number(port);
+ }
+
+ request.setHeaderField("Host", host);
+ }
+
+ reply->d_func()->requestIsPrepared = true;
+}
+
+bool QHttpNetworkConnectionPrivate::ensureConnection(QAbstractSocket *socket)
+{
+ // make sure that this socket is in a connected state, if not initiate
+ // connection to the host.
+ if (socket->state() != QAbstractSocket::ConnectedState) {
+ // connect to the host if not already connected.
+ int index = indexOf(socket);
+ channels[index].state = ConnectingState;
+ channels[index].pendingEncrypt = encrypt;
+
+ // This workaround is needed since we use QAuthenticator for NTLM authentication. The "phase == Done"
+ // is the usual criteria for emitting authentication signals. The "phase" is set to "Done" when the
+ // last header for Authorization is generated by the QAuthenticator. Basic & Digest logic does not
+ // check the "phase" for generating the Authorization header. NTLM authentication is a two stage
+ // process & needs the "phase". To make sure the QAuthenticator uses the current username/password
+ // the phase is reset to Start.
+ QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[index].authenticator);
+ if (priv && priv->phase == QAuthenticatorPrivate::Done)
+ priv->phase = QAuthenticatorPrivate::Start;
+ priv = QAuthenticatorPrivate::getPrivate(channels[index].proxyAuthenticator);
+ if (priv && priv->phase == QAuthenticatorPrivate::Done)
+ priv->phase = QAuthenticatorPrivate::Start;
+
+ QString connectHost = hostName;
+ qint16 connectPort = port;
+
+#ifndef QT_NO_NETWORKPROXY
+ // HTTPS always use transparent proxy.
+ if (networkProxy.type() != QNetworkProxy::NoProxy && !encrypt) {
+ connectHost = networkProxy.hostName();
+ connectPort = networkProxy.port();
+ }
+#endif
+ if (encrypt) {
+#ifndef QT_NO_OPENSSL
+ QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
+ sslSocket->connectToHostEncrypted(connectHost, connectPort);
+ if (channels[index].ignoreSSLErrors)
+ sslSocket->ignoreSslErrors();
+#else
+ emitReplyError(socket, channels[index].reply, QNetworkReply::ProtocolUnknownError);
+#endif
+ } else {
+ socket->connectToHost(connectHost, connectPort);
+ }
+ return false;
+ }
+ return true;
+}
+
+
+bool QHttpNetworkConnectionPrivate::sendRequest(QAbstractSocket *socket)
+{
+ Q_Q(QHttpNetworkConnection);
+
+ int i = indexOf(socket);
+ switch (channels[i].state) {
+ case IdleState: { // write the header
+ if (!ensureConnection(socket)) {
+ // wait for the connection (and encryption) to be done
+ // sendRequest will be called again from either
+ // _q_connected or _q_encrypted
+ return false;
+ }
+ channels[i].written = 0; // excluding the header
+ channels[i].bytesTotal = 0;
+ if (channels[i].reply) {
+ channels[i].reply->d_func()->clear();
+ channels[i].reply->d_func()->connection = q;
+ channels[i].reply->d_func()->autoDecompress = channels[i].request.d->autoDecompress;
+ }
+ channels[i].state = WritingState;
+ channels[i].pendingEncrypt = false;
+ // if the url contains authentication parameters, use the new ones
+ // both channels will use the new authentication parameters
+ if (!channels[i].request.url().userInfo().isEmpty()) {
+ QUrl url = channels[i].request.url();
+ QAuthenticator &auth = channels[i].authenticator;
+ if (url.userName() != auth.user()
+ || (!url.password().isEmpty() && url.password() != auth.password())) {
+ auth.setUser(url.userName());
+ auth.setPassword(url.password());
+ copyCredentials(i, &auth, false);
+ }
+ // clear the userinfo, since we use the same request for resending
+ // userinfo in url can conflict with the one in the authenticator
+ url.setUserInfo(QString());
+ channels[i].request.setUrl(url);
+ }
+ createAuthorization(socket, channels[i].request);
+#ifndef QT_NO_NETWORKPROXY
+ QByteArray header = QHttpNetworkRequestPrivate::header(channels[i].request,
+ (networkProxy.type() != QNetworkProxy::NoProxy));
+#else
+ QByteArray header = QHttpNetworkRequestPrivate::header(channels[i].request,
+ false);
+#endif
+ socket->write(header);
+ QIODevice *data = channels[i].request.d->data;
+ QHttpNetworkReply *reply = channels[i].reply;
+ if (reply && reply->d_func()->requestDataBuffer.size())
+ data = &channels[i].reply->d_func()->requestDataBuffer;
+ if (data && (data->isOpen() || data->open(QIODevice::ReadOnly))) {
+ if (data->isSequential()) {
+ channels[i].bytesTotal = -1;
+ QObject::connect(data, SIGNAL(readyRead()), q, SLOT(_q_dataReadyReadNoBuffer()));
+ QObject::connect(data, SIGNAL(readChannelFinished()), q, SLOT(_q_dataReadyReadNoBuffer()));
+ } else {
+ channels[i].bytesTotal = data->size();
+ }
+ } else {
+ channels[i].state = WaitingState;
+ break;
+ }
+ // write the initial chunk together with the headers
+ // fall through
+ }
+ case WritingState: { // write the data
+ QIODevice *data = channels[i].request.d->data;
+ if (channels[i].reply->d_func()->requestDataBuffer.size())
+ data = &channels[i].reply->d_func()->requestDataBuffer;
+ if (!data || channels[i].bytesTotal == channels[i].written) {
+ channels[i].state = WaitingState; // now wait for response
+ break;
+ }
+
+ QByteArray chunk;
+ chunk.resize(ChunkSize);
+ qint64 readSize = data->read(chunk.data(), ChunkSize);
+ if (readSize == -1) {
+ // source has reached EOF
+ channels[i].state = WaitingState; // now wait for response
+ } else if (readSize > 0) {
+ // source gave us something useful
+ channels[i].written += socket->write(chunk.data(), readSize);
+ if (channels[i].reply)
+ emit channels[i].reply->dataSendProgress(channels[i].written, channels[i].bytesTotal);
+ }
+ break;
+ }
+ case WaitingState:
+ case ReadingState:
+ case Wait4AuthState:
+ // ignore _q_bytesWritten in these states
+ // fall through
+ default:
+ break;
+ }
+ return true;
+}
+
+bool QHttpNetworkConnectionPrivate::emitSignals(QHttpNetworkReply *reply)
+{
+ // for 401 & 407 don't emit the data signals. Content along with these
+ // responses are send only if the authentication fails.
+ return (reply && reply->d_func()->statusCode != 401 && reply->d_func()->statusCode != 407);
+}
+
+bool QHttpNetworkConnectionPrivate::expectContent(QHttpNetworkReply *reply)
+{
+ // check whether we can expect content after the headers (rfc 2616, sec4.4)
+ if (!reply)
+ return false;
+ if ((reply->d_func()->statusCode >= 100 && reply->d_func()->statusCode < 200)
+ || reply->d_func()->statusCode == 204 || reply->d_func()->statusCode == 304)
+ return false;
+ if (reply->d_func()->request.operation() == QHttpNetworkRequest::Head)
+ return !emitSignals(reply);
+ if (reply->d_func()->contentLength() == 0)
+ return false;
+ return true;
+}
+
+void QHttpNetworkConnectionPrivate::emitReplyError(QAbstractSocket *socket,
+ QHttpNetworkReply *reply,
+ QNetworkReply::NetworkError errorCode)
+{
+ Q_Q(QHttpNetworkConnection);
+ if (socket && reply) {
+ // this error matters only to this reply
+ reply->d_func()->errorString = errorDetail(errorCode, socket);
+ emit reply->finishedWithError(errorCode, reply->d_func()->errorString);
+ int i = indexOf(socket);
+ // remove the corrupt data if any
+ eraseData(channels[i].reply);
+ closeChannel(i);
+ // send the next request
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+ }
+}
+
+#ifndef QT_NO_COMPRESS
+bool QHttpNetworkConnectionPrivate::expand(QAbstractSocket *socket, QHttpNetworkReply *reply, bool dataComplete)
+{
+ Q_ASSERT(socket);
+ Q_ASSERT(reply);
+
+ qint64 total = bytesAvailable(*reply, true);
+ if (total >= CHUNK || dataComplete) {
+ int i = indexOf(socket);
+ // uncompress the data
+ QByteArray content, inflated;
+ read(*reply, content, -1, true);
+ int ret = Z_OK;
+ if (content.size())
+ ret = reply->d_func()->gunzipBodyPartially(content, inflated);
+ int retCheck = (dataComplete) ? Z_STREAM_END : Z_OK;
+ if (ret >= retCheck) {
+ if (inflated.size()) {
+ reply->d_func()->totalProgress += inflated.size();
+ appendData(*reply, inflated, false);
+ if (emitSignals(reply)) {
+ emit reply->readyRead();
+ // make sure that the reply is valid
+ if (channels[i].reply != reply)
+ return true;
+ emit reply->dataReadProgress(reply->d_func()->totalProgress, 0);
+ // make sure that the reply is valid
+ if (channels[i].reply != reply)
+ return true;
+
+ }
+ }
+ } else {
+ emitReplyError(socket, reply, QNetworkReply::ProtocolFailure);
+ return false;
+ }
+ }
+ return true;
+}
+#endif
+
+void QHttpNetworkConnectionPrivate::receiveReply(QAbstractSocket *socket, QHttpNetworkReply *reply)
+{
+ Q_ASSERT(socket);
+
+ Q_Q(QHttpNetworkConnection);
+ qint64 bytes = 0;
+ QAbstractSocket::SocketState state = socket->state();
+ int i = indexOf(socket);
+
+ // connection might be closed to signal the end of data
+ if (state == QAbstractSocket::UnconnectedState) {
+ if (!socket->bytesAvailable()) {
+ if (reply && reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
+ reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
+ channels[i].state = IdleState;
+ allDone(socket, reply);
+ } else {
+ // try to reconnect/resend before sending an error.
+ if (channels[i].reconnectAttempts-- > 0) {
+ resendCurrentRequest(socket);
+ } else {
+ reply->d_func()->errorString = errorDetail(QNetworkReply::RemoteHostClosedError, socket);
+ emit reply->finishedWithError(QNetworkReply::RemoteHostClosedError, reply->d_func()->errorString);
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+ }
+ }
+ }
+ }
+
+ // read loop for the response
+ while (socket->bytesAvailable()) {
+ QHttpNetworkReplyPrivate::ReplyState state = reply ? reply->d_func()->state : QHttpNetworkReplyPrivate::AllDoneState;
+ switch (state) {
+ case QHttpNetworkReplyPrivate::NothingDoneState:
+ case QHttpNetworkReplyPrivate::ReadingStatusState:
+ bytes += reply->d_func()->readStatus(socket);
+ channels[i].lastStatus = reply->d_func()->statusCode;
+ break;
+ case QHttpNetworkReplyPrivate::ReadingHeaderState:
+ bytes += reply->d_func()->readHeader(socket);
+ if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
+ if (reply->d_func()->isGzipped() && reply->d_func()->autoDecompress) {
+ // remove the Content-Length from header
+ reply->d_func()->removeAutoDecompressHeader();
+ } else {
+ reply->d_func()->autoDecompress = false;
+ }
+ if (reply && reply->d_func()->statusCode == 100) {
+ reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState;
+ break; // ignore
+ }
+ if (emitSignals(reply))
+ emit reply->headerChanged();
+ if (!expectContent(reply)) {
+ reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
+ channels[i].state = IdleState;
+ allDone(socket, reply);
+ return;
+ }
+ }
+ break;
+ case QHttpNetworkReplyPrivate::ReadingDataState: {
+ QBuffer fragment;
+ fragment.open(QIODevice::WriteOnly);
+ bytes = reply->d_func()->readBody(socket, &fragment);
+ if (bytes) {
+ appendData(*reply, fragment.data(), reply->d_func()->autoDecompress);
+ if (!reply->d_func()->autoDecompress) {
+ reply->d_func()->totalProgress += fragment.size();
+ if (emitSignals(reply)) {
+ emit reply->readyRead();
+ // make sure that the reply is valid
+ if (channels[i].reply != reply)
+ return;
+ emit reply->dataReadProgress(reply->d_func()->totalProgress, reply->d_func()->bodyLength);
+ // make sure that the reply is valid
+ if (channels[i].reply != reply)
+ return;
+ }
+ }
+#ifndef QT_NO_COMPRESS
+ else if (!expand(socket, reply, false)) { // expand a chunk if possible
+ return; // ### expand failed
+ }
+#endif
+ }
+ if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState)
+ break;
+ // everything done, fall through
+ }
+ case QHttpNetworkReplyPrivate::AllDoneState:
+ channels[i].state = IdleState;
+ allDone(socket, reply);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void QHttpNetworkConnectionPrivate::allDone(QAbstractSocket *socket, QHttpNetworkReply *reply)
+{
+#ifndef QT_NO_COMPRESS
+ // expand the whole data.
+ if (expectContent(reply) && reply->d_func()->autoDecompress && !reply->d_func()->streamEnd)
+ expand(socket, reply, true); // ### if expand returns false, its an error
+#endif
+ // while handling 401 & 407, we might reset the status code, so save this.
+ bool emitFinished = emitSignals(reply);
+ handleStatus(socket, reply);
+ // ### at this point there should be no more data on the socket
+ // close if server requested
+ int i = indexOf(socket);
+ if (reply->d_func()->connectionCloseEnabled())
+ closeChannel(i);
+ // queue the finished signal, this is required since we might send new requests from
+ // slot connected to it. The socket will not fire readyRead signal, if we are already
+ // in the slot connected to readyRead
+ if (emitFinished)
+ QMetaObject::invokeMethod(reply, "finished", Qt::QueuedConnection);
+ // reset the reconnection attempts after we receive a complete reply.
+ // in case of failures, each channel will attempt two reconnects before emitting error.
+ channels[i].reconnectAttempts = 2;
+}
+
+void QHttpNetworkConnectionPrivate::handleStatus(QAbstractSocket *socket, QHttpNetworkReply *reply)
+{
+ Q_ASSERT(socket);
+ Q_ASSERT(reply);
+
+ Q_Q(QHttpNetworkConnection);
+
+ int statusCode = reply->statusCode();
+ bool resend = false;
+
+ switch (statusCode) {
+ case 401:
+ case 407:
+ handleAuthenticateChallenge(socket, reply, (statusCode == 407), resend);
+ if (resend) {
+ eraseData(reply);
+ sendRequest(socket);
+ }
+ break;
+ default:
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+ }
+}
+
+void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy)
+{
+ Q_ASSERT(auth);
+
+ // select another channel
+ QAuthenticator* otherAuth = 0;
+ for (int i = 0; i < channelCount; ++i) {
+ if (i == fromChannel)
+ continue;
+ if (isProxy)
+ otherAuth = &channels[i].proxyAuthenticator;
+ else
+ otherAuth = &channels[i].authenticator;
+ // if the credentials are different, copy them
+ if (otherAuth->user().compare(auth->user()))
+ otherAuth->setUser(auth->user());
+ if (otherAuth->password().compare(auth->password()))
+ otherAuth->setPassword(auth->password());
+ }
+}
+
+
+bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply,
+ bool isProxy, bool &resend)
+{
+ Q_ASSERT(socket);
+ Q_ASSERT(reply);
+
+ Q_Q(QHttpNetworkConnection);
+
+ resend = false;
+ //create the response header to be used with QAuthenticatorPrivate.
+ QHttpResponseHeader responseHeader;
+ QList<QPair<QByteArray, QByteArray> > fields = reply->header();
+ QList<QPair<QByteArray, QByteArray> >::const_iterator it = fields.constBegin();
+ while (it != fields.constEnd()) {
+ responseHeader.addValue(QString::fromLatin1(it->first), QString::fromUtf8(it->second));
+ it++;
+ }
+ //find out the type of authentication protocol requested.
+ QAuthenticatorPrivate::Method authMethod = reply->d_func()->authenticationMethod(isProxy);
+ if (authMethod != QAuthenticatorPrivate::None) {
+ int i = indexOf(socket);
+ //Use a single authenticator for all domains. ### change later to use domain/realm
+ QAuthenticator* auth = 0;
+ if (isProxy) {
+ auth = &channels[i].proxyAuthenticator;
+ channels[i].proxyAuthMehtod = authMethod;
+ } else {
+ auth = &channels[i].authenticator;
+ channels[i].authMehtod = authMethod;
+ }
+ //proceed with the authentication.
+ if (auth->isNull())
+ auth->detach();
+ QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth);
+ priv->parseHttpResponse(responseHeader, isProxy);
+
+ if (priv->phase == QAuthenticatorPrivate::Done) {
+ if ((isProxy && pendingProxyAuthSignal) ||(!isProxy && pendingAuthSignal)) {
+ // drop the request
+ eraseData(channels[i].reply);
+ closeChannel(i);
+ channels[i].lastStatus = 0;
+ channels[i].state = Wait4AuthState;
+ return false;
+ }
+ // cannot use this socket until the slot returns
+ channels[i].state = WaitingState;
+ socket->blockSignals(true);
+ if (!isProxy) {
+ pendingAuthSignal = true;
+ emit q->authenticationRequired(reply->request(), auth, q);
+ pendingAuthSignal = false;
+#ifndef QT_NO_NETWORKPROXY
+ } else {
+ pendingProxyAuthSignal = true;
+ emit q->proxyAuthenticationRequired(networkProxy, auth, q);
+ pendingProxyAuthSignal = false;
+#endif
+ }
+ socket->blockSignals(false);
+ // socket free to use
+ channels[i].state = IdleState;
+ if (priv->phase != QAuthenticatorPrivate::Done) {
+ // send any pending requests
+ copyCredentials(i, auth, isProxy);
+ QMetaObject::invokeMethod(q, "_q_restartPendingRequest", Qt::QueuedConnection);
+ }
+ }
+ // changing values in QAuthenticator will reset the 'phase'
+ if (priv->phase == QAuthenticatorPrivate::Done) {
+ // authentication is cancelled, send the current contents to the user.
+ emit channels[i].reply->headerChanged();
+ emit channels[i].reply->readyRead();
+ emit channels[i].reply->finished();
+ QNetworkReply::NetworkError errorCode =
+ isProxy
+ ? QNetworkReply::ProxyAuthenticationRequiredError
+ : QNetworkReply::AuthenticationRequiredError;
+ reply->d_func()->errorString = errorDetail(errorCode, socket);
+ emit q->error(errorCode, reply->d_func()->errorString);
+ emit channels[i].reply->finished();
+ // ### at this point the reply could be deleted
+ socket->close();
+ // remove pending request on the other channels
+ for (int j = 0; j < channelCount; ++j) {
+ if (j != i && channels[j].state == Wait4AuthState)
+ channels[j].state = IdleState;
+ }
+ return true;
+ }
+ //resend the request
+ resend = true;
+ return true;
+ }
+ return false;
+}
+
+void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request)
+{
+ Q_ASSERT(socket);
+
+ int i = indexOf(socket);
+ if (channels[i].authMehtod != QAuthenticatorPrivate::None) {
+ if (!(channels[i].authMehtod == QAuthenticatorPrivate::Ntlm && channels[i].lastStatus != 401)) {
+ QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].authenticator);
+ if (priv && priv->method != QAuthenticatorPrivate::None) {
+ QByteArray response = priv->calculateResponse(request.d->methodName(), request.d->uri(false));
+ request.setHeaderField("authorization", response);
+ }
+ }
+ }
+ if (channels[i].proxyAuthMehtod != QAuthenticatorPrivate::None) {
+ if (!(channels[i].proxyAuthMehtod == QAuthenticatorPrivate::Ntlm && channels[i].lastStatus != 407)) {
+ QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].proxyAuthenticator);
+ if (priv && priv->method != QAuthenticatorPrivate::None) {
+ QByteArray response = priv->calculateResponse(request.d->methodName(), request.d->uri(false));
+ request.setHeaderField("proxy-authorization", response);
+ }
+ }
+ }
+}
+
+QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetworkRequest &request)
+{
+ Q_Q(QHttpNetworkConnection);
+
+ // The reply component of the pair is created initially.
+ QHttpNetworkReply *reply = new QHttpNetworkReply(request.url());
+ reply->setRequest(request);
+ reply->d_func()->connection = q;
+ HttpMessagePair pair = qMakePair(request, reply);
+
+ switch (request.priority()) {
+ case QHttpNetworkRequest::HighPriority:
+ highPriorityQueue.prepend(pair);
+ break;
+ case QHttpNetworkRequest::NormalPriority:
+ case QHttpNetworkRequest::LowPriority:
+ lowPriorityQueue.prepend(pair);
+ break;
+ }
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+ return reply;
+}
+
+void QHttpNetworkConnectionPrivate::unqueueRequest(QAbstractSocket *socket)
+{
+ Q_ASSERT(socket);
+
+ int i = indexOf(socket);
+
+ if (!highPriorityQueue.isEmpty()) {
+ for (int j = highPriorityQueue.count() - 1; j >= 0; --j) {
+ HttpMessagePair &messagePair = highPriorityQueue[j];
+ if (!messagePair.second->d_func()->requestIsPrepared)
+ prepareRequest(messagePair);
+ if (!messagePair.second->d_func()->requestIsBuffering) {
+ channels[i].request = messagePair.first;
+ channels[i].reply = messagePair.second;
+ sendRequest(socket);
+ highPriorityQueue.removeAt(j);
+ return;
+ }
+ }
+ }
+
+ if (!lowPriorityQueue.isEmpty()) {
+ for (int j = lowPriorityQueue.count() - 1; j >= 0; --j) {
+ HttpMessagePair &messagePair = lowPriorityQueue[j];
+ if (!messagePair.second->d_func()->requestIsPrepared)
+ prepareRequest(messagePair);
+ if (!messagePair.second->d_func()->requestIsBuffering) {
+ channels[i].request = messagePair.first;
+ channels[i].reply = messagePair.second;
+ sendRequest(socket);
+ lowPriorityQueue.removeAt(j);
+ return;
+ }
+ }
+ }
+}
+
+void QHttpNetworkConnectionPrivate::closeChannel(int channel)
+{
+ QAbstractSocket *socket = channels[channel].socket;
+ socket->blockSignals(true);
+ socket->close();
+ socket->blockSignals(false);
+ channels[channel].state = IdleState;
+}
+
+void QHttpNetworkConnectionPrivate::resendCurrentRequest(QAbstractSocket *socket)
+{
+ Q_Q(QHttpNetworkConnection);
+ Q_ASSERT(socket);
+ int i = indexOf(socket);
+ closeChannel(i);
+ channels[i].resendCurrent = true;
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+}
+
+QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket* socket)
+{
+ Q_ASSERT(socket);
+
+ QString errorString;
+ switch (errorCode) {
+ case QNetworkReply::HostNotFoundError:
+ errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QHttp", "Host %1 not found"))
+ .arg(socket->peerName());
+ break;
+ case QNetworkReply::ConnectionRefusedError:
+ errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Connection refused"));
+ break;
+ case QNetworkReply::RemoteHostClosedError:
+ errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Connection closed"));
+ break;
+ case QNetworkReply::TimeoutError:
+ errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "HTTP request failed"));
+ break;
+ case QNetworkReply::ProxyAuthenticationRequiredError:
+ errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Proxy requires authentication"));
+ break;
+ case QNetworkReply::AuthenticationRequiredError:
+ errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Host requires authentication"));
+ break;
+ case QNetworkReply::ProtocolFailure:
+ errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Data corrupted"));
+ break;
+ case QNetworkReply::ProtocolUnknownError:
+ errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Unknown protocol specified"));
+ break;
+ case QNetworkReply::SslHandshakeFailedError:
+ errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "SSL handshake failed"));
+ break;
+ default:
+ // all other errors are treated as QNetworkReply::UnknownNetworkError
+ errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "HTTP request failed"));
+ break;
+ }
+ return errorString;
+}
+
+void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply)
+{
+ Q_Q(QHttpNetworkConnection);
+
+ // remove the from active list.
+ for (int i = 0; i < channelCount; ++i) {
+ if (channels[i].reply == reply) {
+ channels[i].reply = 0;
+ closeChannel(i);
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+ return;
+ }
+ }
+ // remove from the high priority queue
+ if (!highPriorityQueue.isEmpty()) {
+ for (int j = highPriorityQueue.count() - 1; j >= 0; --j) {
+ HttpMessagePair messagePair = highPriorityQueue.at(j);
+ if (messagePair.second == reply) {
+ highPriorityQueue.removeAt(j);
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+ return;
+ }
+ }
+ }
+ // remove from the low priority queue
+ if (!lowPriorityQueue.isEmpty()) {
+ for (int j = lowPriorityQueue.count() - 1; j >= 0; --j) {
+ HttpMessagePair messagePair = lowPriorityQueue.at(j);
+ if (messagePair.second == reply) {
+ lowPriorityQueue.removeAt(j);
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+ return;
+ }
+ }
+ }
+}
+
+
+//private slots
+void QHttpNetworkConnectionPrivate::_q_readyRead()
+{
+ Q_Q(QHttpNetworkConnection);
+ QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender());
+ if (!socket)
+ return; // ### error
+ if (isSocketWaiting(socket) || isSocketReading(socket)) {
+ int i = indexOf(socket);
+ channels[i].state = ReadingState;
+ if (channels[i].reply)
+ receiveReply(socket, channels[i].reply);
+ }
+ // ### error
+}
+
+void QHttpNetworkConnectionPrivate::_q_bytesWritten(qint64 bytes)
+{
+ Q_UNUSED(bytes);
+ Q_Q(QHttpNetworkConnection);
+ QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender());
+ if (!socket)
+ return; // ### error
+ if (isSocketWriting(socket))
+ sendRequest(socket);
+ // otherwise we do nothing
+}
+
+void QHttpNetworkConnectionPrivate::_q_disconnected()
+{
+ Q_Q(QHttpNetworkConnection);
+ QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender());
+ if (!socket)
+ return; // ### error
+ // read the available data before closing
+ int i = indexOf(socket);
+ if (isSocketWaiting(socket) || isSocketReading(socket)) {
+ channels[i].state = ReadingState;
+ if (channels[i].reply)
+ receiveReply(socket, channels[i].reply);
+ }
+ channels[i].state = IdleState;
+}
+
+void QHttpNetworkConnectionPrivate::_q_startNextRequest()
+{
+ // send the current request again
+ if (channels[0].resendCurrent || channels[1].resendCurrent) {
+ int i = channels[0].resendCurrent ? 0:1;
+ QAbstractSocket *socket = channels[i].socket;
+ channels[i].resendCurrent = false;
+ channels[i].state = IdleState;
+ if (channels[i].reply)
+ sendRequest(socket);
+ return;
+ }
+ // send the request using the idle socket
+ QAbstractSocket *socket = channels[0].socket;
+ if (isSocketBusy(socket)) {
+ socket = (isSocketBusy(channels[1].socket) ? 0 :channels[1].socket);
+ }
+
+ if (!socket) {
+ return; // this will be called after finishing current request.
+ }
+ unqueueRequest(socket);
+}
+
+void QHttpNetworkConnectionPrivate::_q_restartPendingRequest()
+{
+ // send the request using the idle socket
+ for (int i = 0 ; i < channelCount; ++i) {
+ QAbstractSocket *socket = channels[i].socket;
+ if (channels[i].state == Wait4AuthState) {
+ channels[i].state = IdleState;
+ if (channels[i].reply)
+ sendRequest(socket);
+ }
+ }
+}
+
+void QHttpNetworkConnectionPrivate::_q_connected()
+{
+ Q_Q(QHttpNetworkConnection);
+ QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender());
+ if (!socket)
+ return; // ### error
+ int i = indexOf(socket);
+ // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
+ //channels[i].reconnectAttempts = 2;
+ if (!channels[i].pendingEncrypt) {
+ channels[i].state = IdleState;
+ if (channels[i].reply)
+ sendRequest(socket);
+ else
+ closeChannel(i);
+ }
+}
+
+
+void QHttpNetworkConnectionPrivate::_q_error(QAbstractSocket::SocketError socketError)
+{
+ Q_Q(QHttpNetworkConnection);
+ QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender());
+ if (!socket)
+ return;
+ bool send2Reply = false;
+ int i = indexOf(socket);
+ QNetworkReply::NetworkError errorCode = QNetworkReply::UnknownNetworkError;
+
+ switch (socketError) {
+ case QAbstractSocket::HostNotFoundError:
+ errorCode = QNetworkReply::HostNotFoundError;
+ break;
+ case QAbstractSocket::ConnectionRefusedError:
+ errorCode = QNetworkReply::ConnectionRefusedError;
+ break;
+ case QAbstractSocket::RemoteHostClosedError:
+ // try to reconnect/resend before sending an error.
+ // while "Reading" the _q_disconnected() will handle this.
+ if (channels[i].state != IdleState && channels[i].state != ReadingState) {
+ if (channels[i].reconnectAttempts-- > 0) {
+ resendCurrentRequest(socket);
+ return;
+ } else {
+ send2Reply = true;
+ errorCode = QNetworkReply::RemoteHostClosedError;
+ }
+ } else {
+ return;
+ }
+ break;
+ case QAbstractSocket::SocketTimeoutError:
+ // try to reconnect/resend before sending an error.
+ if (channels[i].state == WritingState && (channels[i].reconnectAttempts-- > 0)) {
+ resendCurrentRequest(socket);
+ return;
+ }
+ send2Reply = true;
+ errorCode = QNetworkReply::TimeoutError;
+ break;
+ case QAbstractSocket::ProxyAuthenticationRequiredError:
+ errorCode = QNetworkReply::ProxyAuthenticationRequiredError;
+ break;
+ case QAbstractSocket::SslHandshakeFailedError:
+ errorCode = QNetworkReply::SslHandshakeFailedError;
+ break;
+ default:
+ // all other errors are treated as NetworkError
+ errorCode = QNetworkReply::UnknownNetworkError;
+ break;
+ }
+ QPointer<QObject> that = q;
+ QString errorString = errorDetail(errorCode, socket);
+ if (send2Reply) {
+ if (channels[i].reply) {
+ channels[i].reply->d_func()->errorString = errorString;
+ // this error matters only to this reply
+ emit channels[i].reply->finishedWithError(errorCode, errorString);
+ }
+ // send the next request
+ QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
+ } else {
+ // the failure affects all requests.
+ emit q->error(errorCode, errorString);
+ }
+ if (that) //signals make enter the event loop
+ closeChannel(i);
+}
+
+#ifndef QT_NO_NETWORKPROXY
+void QHttpNetworkConnectionPrivate::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth)
+{
+ Q_Q(QHttpNetworkConnection);
+ emit q->proxyAuthenticationRequired(proxy, auth, q);
+}
+#endif
+
+void QHttpNetworkConnectionPrivate::_q_dataReadyReadNoBuffer()
+{
+ Q_Q(QHttpNetworkConnection);
+ // data emitted either readyRead()
+ // find out which channel it is for
+ QIODevice *sender = qobject_cast<QIODevice *>(q->sender());
+
+ // won't match anything if the qobject_cast above failed
+ for (int i = 0; i < channelCount; ++i) {
+ if (sender == channels[i].request.data()) {
+ sendRequest(channels[i].socket);
+ break;
+ }
+ }
+}
+
+void QHttpNetworkConnectionPrivate::_q_dataReadyReadBuffer()
+{
+ Q_Q(QHttpNetworkConnection);
+ QIODevice *sender = qobject_cast<QIODevice *>(q->sender());
+ HttpMessagePair *thePair = 0;
+ for (int i = 0; !thePair && i < lowPriorityQueue.size(); ++i)
+ if (lowPriorityQueue.at(i).first.data() == sender)
+ thePair = &lowPriorityQueue[i];
+
+ for (int i = 0; !thePair && i < highPriorityQueue.size(); ++i)
+ if (highPriorityQueue.at(i).first.data() == sender)
+ thePair = &highPriorityQueue[i];
+
+ if (thePair) {
+ bufferData(*thePair);
+
+ // are we finished buffering?
+ if (!thePair->second->d_func()->requestIsBuffering)
+ _q_startNextRequest();
+ }
+}
+
+void QHttpNetworkConnectionPrivate::bufferData(HttpMessagePair &messagePair)
+{
+ Q_Q(QHttpNetworkConnection);
+ QHttpNetworkRequest &request = messagePair.first;
+ QHttpNetworkReply *reply = messagePair.second;
+ Q_ASSERT(request.data());
+ if (!reply->d_func()->requestIsBuffering) { // first time
+ QObject::connect(request.data(), SIGNAL(readyRead()), q, SLOT(_q_dataReadyReadBuffer()));
+ QObject::connect(request.data(), SIGNAL(readChannelFinished()), q, SLOT(_q_dataReadyReadBuffer()));
+ reply->d_func()->requestIsBuffering = true;
+ reply->d_func()->requestDataBuffer.open(QIODevice::ReadWrite);
+ }
+
+ // always try to read at least one byte
+ // ### FIXME! use a QRingBuffer
+ qint64 bytesToRead = qMax<qint64>(1, request.data()->bytesAvailable());
+ QByteArray newData;
+ newData.resize(bytesToRead);
+ qint64 bytesActuallyRead = request.data()->read(newData.data(), bytesToRead);
+
+ if (bytesActuallyRead > 0) {
+ // we read something
+ newData.chop(bytesToRead - bytesActuallyRead);
+ reply->d_func()->requestDataBuffer.write(newData);
+ } else if (bytesActuallyRead == -1) { // last time
+ QObject::disconnect(request.data(), SIGNAL(readyRead()), q, SLOT(_q_dataReadyReadBuffer()));
+ QObject::disconnect(request.data(), SIGNAL(readChannelFinished()), q, SLOT(_q_dataReadyReadBuffer()));
+
+ request.setContentLength(reply->d_func()->requestDataBuffer.size());
+ reply->d_func()->requestDataBuffer.seek(0);
+ reply->d_func()->requestIsBuffering = false;
+ }
+}
+
+// QHttpNetworkConnection
+
+QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent)
+ : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent)
+{
+ Q_D(QHttpNetworkConnection);
+ d->init();
+}
+
+QHttpNetworkConnection::~QHttpNetworkConnection()
+{
+}
+
+QString QHttpNetworkConnection::hostName() const
+{
+ Q_D(const QHttpNetworkConnection);
+ return d->hostName;
+}
+
+quint16 QHttpNetworkConnection::port() const
+{
+ Q_D(const QHttpNetworkConnection);
+ return d->port;
+}
+
+QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest &request)
+{
+ Q_D(QHttpNetworkConnection);
+ return d->queueRequest(request);
+}
+
+void QHttpNetworkConnection::enableEncryption()
+{
+ Q_D(QHttpNetworkConnection);
+ d->encrypt = true;
+}
+
+bool QHttpNetworkConnection::isEncrypted() const
+{
+ Q_D(const QHttpNetworkConnection);
+ return d->encrypt;
+}
+
+void QHttpNetworkConnection::setProxyAuthentication(QAuthenticator *authenticator)
+{
+ Q_D(QHttpNetworkConnection);
+ for (int i = 0; i < d->channelCount; ++i)
+ d->channels[i].proxyAuthenticator = *authenticator;
+}
+
+void QHttpNetworkConnection::setAuthentication(const QString &domain, QAuthenticator *authenticator)
+{
+ Q_UNUSED(domain); // ### domain ?
+ Q_D(QHttpNetworkConnection);
+ for (int i = 0; i < d->channelCount; ++i)
+ d->channels[i].authenticator = *authenticator;
+}
+
+#ifndef QT_NO_NETWORKPROXY
+void QHttpNetworkConnection::setCacheProxy(const QNetworkProxy &networkProxy)
+{
+ Q_D(QHttpNetworkConnection);
+ d->networkProxy = networkProxy;
+ // update the authenticator
+ if (!d->networkProxy.user().isEmpty()) {
+ for (int i = 0; i < d->channelCount; ++i) {
+ d->channels[i].proxyAuthenticator.setUser(d->networkProxy.user());
+ d->channels[i].proxyAuthenticator.setPassword(d->networkProxy.password());
+ }
+ }
+}
+
+QNetworkProxy QHttpNetworkConnection::cacheProxy() const
+{
+ Q_D(const QHttpNetworkConnection);
+ return d->networkProxy;
+}
+
+void QHttpNetworkConnection::setTransparentProxy(const QNetworkProxy &networkProxy)
+{
+ Q_D(QHttpNetworkConnection);
+ for (int i = 0; i < d->channelCount; ++i)
+ d->channels[i].socket->setProxy(networkProxy);
+}
+
+QNetworkProxy QHttpNetworkConnection::transparentProxy() const
+{
+ Q_D(const QHttpNetworkConnection);
+ return d->channels[0].socket->proxy();
+}
+#endif
+
+// QHttpNetworkRequest
+
+QHttpNetworkRequest::QHttpNetworkRequest(const QUrl &url, Operation operation, Priority priority)
+ : d(new QHttpNetworkRequestPrivate(operation, priority, url))
+{
+}
+
+QHttpNetworkRequest::QHttpNetworkRequest(const QHttpNetworkRequest &other)
+ : QHttpNetworkHeader(other), d(other.d)
+{
+}
+
+QHttpNetworkRequest::~QHttpNetworkRequest()
+{
+}
+
+QUrl QHttpNetworkRequest::url() const
+{
+ return d->url;
+}
+void QHttpNetworkRequest::setUrl(const QUrl &url)
+{
+ d->url = url;
+}
+
+qint64 QHttpNetworkRequest::contentLength() const
+{
+ return d->contentLength();
+}
+
+void QHttpNetworkRequest::setContentLength(qint64 length)
+{
+ d->setContentLength(length);
+}
+
+QList<QPair<QByteArray, QByteArray> > QHttpNetworkRequest::header() const
+{
+ return d->fields;
+}
+
+QByteArray QHttpNetworkRequest::headerField(const QByteArray &name, const QByteArray &defaultValue) const
+{
+ return d->headerField(name, defaultValue);
+}
+
+void QHttpNetworkRequest::setHeaderField(const QByteArray &name, const QByteArray &data)
+{
+ d->setHeaderField(name, data);
+}
+
+QHttpNetworkRequest &QHttpNetworkRequest::operator=(const QHttpNetworkRequest &other)
+{
+ d = other.d;
+ return *this;
+}
+
+bool QHttpNetworkRequest::operator==(const QHttpNetworkRequest &other) const
+{
+ return d->operator==(*other.d);
+}
+
+QHttpNetworkRequest::Operation QHttpNetworkRequest::operation() const
+{
+ return d->operation;
+}
+
+void QHttpNetworkRequest::setOperation(Operation operation)
+{
+ d->operation = operation;
+}
+
+QHttpNetworkRequest::Priority QHttpNetworkRequest::priority() const
+{
+ return d->priority;
+}
+
+void QHttpNetworkRequest::setPriority(Priority priority)
+{
+ d->priority = priority;
+}
+
+QIODevice *QHttpNetworkRequest::data() const
+{
+ return d->data;
+}
+
+void QHttpNetworkRequest::setData(QIODevice *data)
+{
+ d->data = data;
+}
+
+int QHttpNetworkRequest::majorVersion() const
+{
+ return 1;
+}
+
+int QHttpNetworkRequest::minorVersion() const
+{
+ return 1;
+}
+
+// QHttpNetworkReply
+
+QHttpNetworkReply::QHttpNetworkReply(const QUrl &url, QObject *parent)
+ : QObject(*new QHttpNetworkReplyPrivate(url), parent)
+{
+}
+
+QHttpNetworkReply::~QHttpNetworkReply()
+{
+ Q_D(QHttpNetworkReply);
+ if (d->connection) {
+ d->connection->d_func()->removeReply(this);
+ }
+}
+
+QUrl QHttpNetworkReply::url() const
+{
+ return d_func()->url;
+}
+void QHttpNetworkReply::setUrl(const QUrl &url)
+{
+ Q_D(QHttpNetworkReply);
+ d->url = url;
+}
+
+qint64 QHttpNetworkReply::contentLength() const
+{
+ return d_func()->contentLength();
+}
+
+void QHttpNetworkReply::setContentLength(qint64 length)
+{
+ Q_D(QHttpNetworkReply);
+ d->setContentLength(length);
+}
+
+QList<QPair<QByteArray, QByteArray> > QHttpNetworkReply::header() const
+{
+ return d_func()->fields;
+}
+
+QByteArray QHttpNetworkReply::headerField(const QByteArray &name, const QByteArray &defaultValue) const
+{
+ return d_func()->headerField(name, defaultValue);
+}
+
+void QHttpNetworkReply::setHeaderField(const QByteArray &name, const QByteArray &data)
+{
+ Q_D(QHttpNetworkReply);
+ d->setHeaderField(name, data);
+}
+
+void QHttpNetworkReply::parseHeader(const QByteArray &header)
+{
+ Q_D(QHttpNetworkReply);
+ d->parseHeader(header);
+}
+
+QHttpNetworkRequest QHttpNetworkReply::request() const
+{
+ return d_func()->request;
+}
+
+void QHttpNetworkReply::setRequest(const QHttpNetworkRequest &request)
+{
+ Q_D(QHttpNetworkReply);
+ d->request = request;
+}
+
+int QHttpNetworkReply::statusCode() const
+{
+ return d_func()->statusCode;
+}
+
+void QHttpNetworkReply::setStatusCode(int code)
+{
+ Q_D(QHttpNetworkReply);
+ d->statusCode = code;
+}
+
+QString QHttpNetworkReply::errorString() const
+{
+ return d_func()->errorString;
+}
+
+QString QHttpNetworkReply::reasonPhrase() const
+{
+ return d_func()->reasonPhrase;
+}
+
+void QHttpNetworkReply::setErrorString(const QString &error)
+{
+ Q_D(QHttpNetworkReply);
+ d->errorString = error;
+}
+
+int QHttpNetworkReply::majorVersion() const
+{
+ return d_func()->majorVersion;
+}
+
+int QHttpNetworkReply::minorVersion() const
+{
+ return d_func()->minorVersion;
+}
+
+qint64 QHttpNetworkReply::bytesAvailable() const
+{
+ Q_D(const QHttpNetworkReply);
+ if (d->connection)
+ return d->connection->d_func()->bytesAvailable(*this);
+ else
+ return -1;
+}
+
+QByteArray QHttpNetworkReply::read(qint64 maxSize)
+{
+ Q_D(QHttpNetworkReply);
+ QByteArray data;
+ if (d->connection)
+ d->connection->d_func()->read(*this, data, maxSize, false);
+ return data;
+}
+
+bool QHttpNetworkReply::isFinished() const
+{
+ return d_func()->state == QHttpNetworkReplyPrivate::AllDoneState;
+}
+
+// SSL support below
+#ifndef QT_NO_OPENSSL
+void QHttpNetworkConnectionPrivate::_q_encrypted()
+{
+ Q_Q(QHttpNetworkConnection);
+ QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender());
+ if (!socket)
+ return; // ### error
+ channels[indexOf(socket)].state = IdleState;
+ sendRequest(socket);
+}
+
+void QHttpNetworkConnectionPrivate::_q_sslErrors(const QList<QSslError> &errors)
+{
+ Q_Q(QHttpNetworkConnection);
+ QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender());
+ if (!socket)
+ return;
+ //QNetworkReply::NetworkError errorCode = QNetworkReply::ProtocolFailure;
+ emit q->sslErrors(errors);
+}
+
+QSslConfiguration QHttpNetworkConnectionPrivate::sslConfiguration(const QHttpNetworkReply &reply) const
+{
+ for (int i = 0; i < channelCount; ++i)
+ if (channels[i].reply == &reply)
+ return static_cast<QSslSocket *>(channels[0].socket)->sslConfiguration();
+ return QSslConfiguration(); // pending or done request
+}
+
+void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config)
+{
+ Q_D(QHttpNetworkConnection);
+ // set the config on all channels
+ for (int i = 0; i < d->channelCount; ++i)
+ static_cast<QSslSocket *>(d->channels[i].socket)->setSslConfiguration(config);
+}
+
+void QHttpNetworkConnection::ignoreSslErrors(int channel)
+{
+ Q_D(QHttpNetworkConnection);
+ if (channel == -1) { // ignore for all channels
+ for (int i = 0; i < d->channelCount; ++i) {
+ static_cast<QSslSocket *>(d->channels[i].socket)->ignoreSslErrors();
+ d->channels[i].ignoreSSLErrors = true;
+ }
+
+ } else {
+ static_cast<QSslSocket *>(d->channels[channel].socket)->ignoreSslErrors();
+ d->channels[channel].ignoreSSLErrors = true;
+ }
+}
+
+QSslConfiguration QHttpNetworkReply::sslConfiguration() const
+{
+ Q_D(const QHttpNetworkReply);
+ if (d->connection)
+ return d->connection->d_func()->sslConfiguration(*this);
+ return QSslConfiguration();
+}
+
+void QHttpNetworkReply::setSslConfiguration(const QSslConfiguration &config)
+{
+ Q_D(QHttpNetworkReply);
+ if (d->connection)
+ d->connection->setSslConfiguration(config);
+}
+
+void QHttpNetworkReply::ignoreSslErrors()
+{
+ Q_D(QHttpNetworkReply);
+ if (d->connection)
+ d->connection->ignoreSslErrors();
+}
+#endif //QT_NO_OPENSSL
+
+
+QT_END_NAMESPACE
+
+#include "moc_qhttpnetworkconnection_p.cpp"
+
+#endif // QT_NO_HTTP