diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:18:55 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:18:55 (GMT) |
commit | e5fcad302d86d316390c6b0f62759a067313e8a9 (patch) | |
tree | c2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/network/access/qhttpnetworkconnection.cpp | |
download | Qt-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.cpp | 2464 |
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, ¤tChunkSize); + 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 |