/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qhttpnetworkreply_p.h" #include "qhttpnetworkconnection_p.h" #ifndef QT_NO_SSL # include # include # include #endif #include QT_BEGIN_NAMESPACE 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; } QUrl QHttpNetworkReply::redirectUrl() const { return d_func()->redirectUrl; } void QHttpNetworkReply::setRedirectUrl(const QUrl &url) { Q_D(QHttpNetworkReply); d->redirectUrl = url; } bool QHttpNetworkReply::isHttpRedirect(int statusCode) { return (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305 || statusCode == 307 || statusCode == 308); } qint64 QHttpNetworkReply::contentLength() const { return d_func()->contentLength(); } void QHttpNetworkReply::setContentLength(qint64 length) { Q_D(QHttpNetworkReply); d->setContentLength(length); } QList > QHttpNetworkReply::header() const { return d_func()->parser.headers(); } 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::appendHeaderField(const QByteArray &name, const QByteArray &data) { Q_D(QHttpNetworkReply); d->appendHeaderField(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; d->ssl = request.isSsl(); } int QHttpNetworkReply::statusCode() const { return d_func()->parser.getStatusCode(); } void QHttpNetworkReply::setStatusCode(int code) { Q_D(QHttpNetworkReply); d->parser.setStatusCode(code); } QString QHttpNetworkReply::errorString() const { return d_func()->errorString; } QNetworkReply::NetworkError QHttpNetworkReply::errorCode() const { return d_func()->httpErrorCode; } QString QHttpNetworkReply::reasonPhrase() const { return d_func()->parser.getReasonPhrase(); } void QHttpNetworkReply::setReasonPhrase(const QString &reason) { d_func()->parser.setReasonPhrase(reason); } void QHttpNetworkReply::setErrorString(const QString &error) { Q_D(QHttpNetworkReply); d->errorString = error; } int QHttpNetworkReply::majorVersion() const { return d_func()->parser.getMajorVersion(); } int QHttpNetworkReply::minorVersion() const { return d_func()->parser.getMinorVersion(); } void QHttpNetworkReply::setMajorVersion(int version) { d_func()->parser.setMajorVersion(version); } void QHttpNetworkReply::setMinorVersion(int version) { d_func()->parser.setMinorVersion(version); } qint64 QHttpNetworkReply::bytesAvailable() const { Q_D(const QHttpNetworkReply); if (d->connection) return d->connection->d_func()->uncompressedBytesAvailable(*this); else return -1; } qint64 QHttpNetworkReply::bytesAvailableNextBlock() const { Q_D(const QHttpNetworkReply); if (d->connection) return d->connection->d_func()->uncompressedBytesAvailableNextBlock(*this); else return -1; } bool QHttpNetworkReply::readAnyAvailable() const { Q_D(const QHttpNetworkReply); return (d->responseData.bufferCount() > 0); } QByteArray QHttpNetworkReply::readAny() { Q_D(QHttpNetworkReply); if (d->responseData.bufferCount() == 0) return QByteArray(); // we'll take the last buffer, so schedule another read from http if (d->downstreamLimited && d->responseData.bufferCount() == 1 && !isFinished()) d->connection->d_func()->readMoreLater(this); return d->responseData.read(); } QByteArray QHttpNetworkReply::readAll() { Q_D(QHttpNetworkReply); return d->responseData.readAll(); } QByteArray QHttpNetworkReply::read(qint64 amount) { Q_D(QHttpNetworkReply); return d->responseData.read(amount); } qint64 QHttpNetworkReply::sizeNextBlock() { Q_D(QHttpNetworkReply); return d->responseData.sizeNextBlock(); } void QHttpNetworkReply::setDownstreamLimited(bool dsl) { Q_D(QHttpNetworkReply); d->downstreamLimited = dsl; d->connection->d_func()->readMoreLater(this); } void QHttpNetworkReply::setReadBufferSize(qint64 size) { Q_D(QHttpNetworkReply); d->readBufferMaxSize = size; } bool QHttpNetworkReply::supportsUserProvidedDownloadBuffer() { Q_D(QHttpNetworkReply); return !d->isChunked() && !d->autoDecompress && d->bodyLength > 0 && d->parser.getStatusCode() == 200; } void QHttpNetworkReply::setUserProvidedDownloadBuffer(char* b) { Q_D(QHttpNetworkReply); if (supportsUserProvidedDownloadBuffer()) d->userProvidedDownloadBuffer = b; } char* QHttpNetworkReply::userProvidedDownloadBuffer() { Q_D(QHttpNetworkReply); return d->userProvidedDownloadBuffer; } void QHttpNetworkReply::abort() { Q_D(QHttpNetworkReply); d->state = QHttpNetworkReplyPrivate::Aborted; } bool QHttpNetworkReply::isAborted() const { return d_func()->state == QHttpNetworkReplyPrivate::Aborted; } bool QHttpNetworkReply::isFinished() const { return d_func()->state == QHttpNetworkReplyPrivate::AllDoneState; } bool QHttpNetworkReply::isPipeliningUsed() const { return d_func()->pipeliningUsed; } bool QHttpNetworkReply::isHttp2Used() const { return d_func()->h2Used; } void QHttpNetworkReply::setHttp2WasUsed(bool h2) { d_func()->h2Used = h2; } qint64 QHttpNetworkReply::removedContentLength() const { return d_func()->removedContentLength; } bool QHttpNetworkReply::isRedirecting() const { return d_func()->isRedirecting(); } QHttpNetworkConnection* QHttpNetworkReply::connection() { return d_func()->connection; } QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) : QHttpNetworkHeaderPrivate(newUrl) , state(NothingDoneState) , ssl(false), bodyLength(0), contentRead(0), totalProgress(0), chunkedTransferEncoding(false), connectionCloseEnabled(true), forceConnectionCloseEnabled(false), lastChunkRead(false), currentChunkSize(0), currentChunkRead(0), readBufferMaxSize(0), totallyUploadedData(0), removedContentLength(-1), connection(nullptr), autoDecompress(false), responseData(), requestIsPrepared(false) ,pipeliningUsed(false), h2Used(false), downstreamLimited(false) ,userProvidedDownloadBuffer(nullptr) { QString scheme = newUrl.scheme(); if (scheme == QLatin1String("preconnect-http") || scheme == QLatin1String("preconnect-https")) // make sure we do not close the socket after preconnecting connectionCloseEnabled = false; } QHttpNetworkReplyPrivate::~QHttpNetworkReplyPrivate() = default; void QHttpNetworkReplyPrivate::clearHttpLayerInformation() { state = NothingDoneState; bodyLength = 0; contentRead = 0; totalProgress = 0; currentChunkSize = 0; currentChunkRead = 0; lastChunkRead = false; connectionCloseEnabled = true; parser.clear(); } // TODO: Isn't everything HTTP layer related? We don't need to set connection and connectionChannel to 0 at all void QHttpNetworkReplyPrivate::clear() { connection = nullptr; connectionChannel = nullptr; autoDecompress = false; clearHttpLayerInformation(); } // QHttpNetworkReplyPrivate qint64 QHttpNetworkReplyPrivate::bytesAvailable() const { return (state != ReadingDataState ? 0 : fragment.size()); } bool QHttpNetworkReplyPrivate::isCompressed() const { return QDecompressHelper::isSupportedEncoding(headerField("content-encoding")); } bool QHttpNetworkReply::isCompressed() const { Q_D(const QHttpNetworkReply); return d->isCompressed(); } void QHttpNetworkReplyPrivate::removeAutoDecompressHeader() { // The header "Content-Encoding = gzip" is retained. // Content-Length is removed since the actual one sent by the server is for compressed data QByteArray name("content-length"); QByteArray contentLength = parser.firstHeaderField(name); bool parseOk = false; qint64 value = contentLength.toLongLong(&parseOk); if (parseOk) { removedContentLength = value; parser.removeHeaderField(name); } } 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 challenges = headerFieldValues(header); for (int i = 0; iread(&c, 1); if (haveRead == -1) return -1; // unexpected EOF else if (haveRead == 0) break; // read more later else if (haveRead == 1 && fragment.size() == 0 && (c == 11 || c == '\n' || c == '\r' || c == ' ' || c == 31)) continue; // Ignore all whitespace that was trailing froma previous request on that socket bytes++; // allow both CRLF & LF (only) line endings if (c == '\n') { // remove the CR at the end if (fragment.endsWith('\r')) { fragment.truncate(fragment.length()-1); } bool ok = parseStatus(fragment); state = ReadingHeaderState; fragment.clear(); if (!ok) { return -1; } break; } else { fragment.append(c); } // is this a valid reply? if (fragment.length() == 5 && !fragment.startsWith("HTTP/")) { fragment.clear(); return -1; } } while (haveRead == 1); return bytes; } bool QHttpNetworkReplyPrivate::parseStatus(const QByteArray &status) { return parser.parseStatus(status); } qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket) { if (fragment.isEmpty()) { // according to http://dev.opera.com/articles/view/mama-http-headers/ the average size of the header // block is 381 bytes. // reserve bytes. This is better than always append() which reallocs the byte array. fragment.reserve(512); } qint64 bytes = 0; char c = 0; bool allHeaders = false; qint64 haveRead = 0; do { haveRead = socket->read(&c, 1); if (haveRead == 0) { // read more later break; } else if (haveRead == -1) { // connection broke down return -1; } else { fragment.append(c); bytes++; if (c == '\n') { // check for possible header endings. As per HTTP rfc, // the header endings will be marked by CRLFCRLF. But // we will allow CRLFCRLF, CRLFLF, LFCRLF, LFLF if (fragment.endsWith("\n\r\n") || fragment.endsWith("\n\n")) allHeaders = true; // there is another case: We have no headers. Then the fragment equals just the line ending if ((fragment.length() == 2 && fragment.endsWith("\r\n")) || (fragment.length() == 1 && fragment.endsWith("\n"))) allHeaders = true; } } } while (!allHeaders && haveRead > 0); // we received all headers now parse them if (allHeaders) { parseHeader(fragment); state = ReadingDataState; fragment.clear(); // next fragment bodyLength = contentLength(); // cache the length // cache isChunked() since it is called often chunkedTransferEncoding = headerField("transfer-encoding").toLower().contains("chunked"); // cache isConnectionCloseEnabled since it is called often QByteArray connectionHeaderField = headerField("connection"); // check for explicit indication of close or the implicit connection close of HTTP/1.0 connectionCloseEnabled = (connectionHeaderField.toLower().contains("close") || headerField("proxy-connection").toLower().contains("close")) || (parser.getMajorVersion() == 1 && parser.getMinorVersion() == 0 && (connectionHeaderField.isEmpty() && !headerField("proxy-connection").toLower().contains("keep-alive"))); } return bytes; } void QHttpNetworkReplyPrivate::parseHeader(const QByteArray &header) { parser.parseHeaders(header); } void QHttpNetworkReplyPrivate::appendHeaderField(const QByteArray &name, const QByteArray &data) { parser.appendHeaderField(name, data); } bool QHttpNetworkReplyPrivate::isChunked() { return chunkedTransferEncoding; } bool QHttpNetworkReplyPrivate::isConnectionCloseEnabled() { return connectionCloseEnabled || forceConnectionCloseEnabled; } // note this function can only be used for non-chunked, non-compressed with // known content length qint64 QHttpNetworkReplyPrivate::readBodyVeryFast(QAbstractSocket *socket, char *b) { // This first read is to flush the buffer inside the socket qint64 haveRead = 0; haveRead = socket->read(b, bodyLength - contentRead); if (haveRead == -1) { return -1; } contentRead += haveRead; if (contentRead == bodyLength) { state = AllDoneState; } return haveRead; } // note this function can only be used for non-chunked, non-compressed with // known content length qint64 QHttpNetworkReplyPrivate::readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb) { qint64 toBeRead = qMin(socket->bytesAvailable(), bodyLength - contentRead); if (readBufferMaxSize) toBeRead = qMin(toBeRead, readBufferMaxSize); if (!toBeRead) return 0; QByteArray bd; bd.resize(toBeRead); qint64 haveRead = socket->read(bd.data(), toBeRead); if (haveRead == -1) { bd.clear(); return 0; // ### error checking here; } bd.resize(haveRead); rb->append(bd); if (contentRead + haveRead == bodyLength) { state = AllDoneState; } contentRead += haveRead; return haveRead; } qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuffer *out) { qint64 bytes = 0; if (isChunked()) { // chunked transfer encoding (rfc 2616, sec 3.6) bytes += readReplyBodyChunked(socket, out); } else if (bodyLength > 0) { // we have a Content-Length bytes += readReplyBodyRaw(socket, out, bodyLength - contentRead); if (contentRead + bytes == bodyLength) state = AllDoneState; } else { // no content length. just read what's possible bytes += readReplyBodyRaw(socket, out, socket->bytesAvailable()); } contentRead += bytes; return bytes; } qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QAbstractSocket *socket, QByteDataBuffer *out, qint64 size) { // FIXME get rid of this function and just use readBodyFast and give it socket->bytesAvailable() qint64 bytes = 0; Q_ASSERT(socket); Q_ASSERT(out); int toBeRead = qMin(128*1024, qMin(size, socket->bytesAvailable())); if (readBufferMaxSize) toBeRead = qMin(toBeRead, readBufferMaxSize); while (toBeRead > 0) { QByteArray byteData; byteData.resize(toBeRead); qint64 haveRead = socket->read(byteData.data(), byteData.size()); if (haveRead <= 0) { // ### error checking here byteData.clear(); return bytes; } byteData.resize(haveRead); out->append(byteData); bytes += haveRead; size -= haveRead; toBeRead = qMin(128*1024, qMin(size, socket->bytesAvailable())); } return bytes; } qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QAbstractSocket *socket, QByteDataBuffer *out) { qint64 bytes = 0; while (socket->bytesAvailable()) { if (readBufferMaxSize && (bytes > readBufferMaxSize)) break; if (!lastChunkRead && currentChunkRead >= currentChunkSize) { // For the first chunk and when we're done with a chunk currentChunkSize = 0; currentChunkRead = 0; if (bytes) { // After a chunk char crlf[2]; // read the "\r\n" after the chunk qint64 haveRead = socket->read(crlf, 2); // FIXME: This code is slightly broken and not optimal. What if the 2 bytes are not available yet?! // For nice reasons (the toLong in getChunkSize accepting \n at the beginning // it right now still works, but we should definitely fix this. if (haveRead != 2) return bytes; // FIXME bytes += haveRead; } // Note that chunk size gets stored in currentChunkSize, what is returned is the bytes read bytes += getChunkSize(socket, ¤tChunkSize); if (currentChunkSize == -1) break; } // if the chunk size is 0, end of the stream if (currentChunkSize == 0 || lastChunkRead) { lastChunkRead = true; // try to read the "\r\n" after the chunk char crlf[2]; qint64 haveRead = socket->read(crlf, 2); if (haveRead > 0) bytes += haveRead; if ((haveRead == 2 && crlf[0] == '\r' && crlf[1] == '\n') || (haveRead == 1 && crlf[0] == '\n')) state = AllDoneState; else if (haveRead == 1 && crlf[0] == '\r') break; // Still waiting for the last \n else if (haveRead > 0) { // If we read something else then CRLF, we need to close the channel. forceConnectionCloseEnabled = true; state = AllDoneState; } break; } // otherwise, try to begin reading this chunk / to read what is missing for this chunk qint64 haveRead = readReplyBodyRaw (socket, out, currentChunkSize - currentChunkRead); currentChunkRead += haveRead; bytes += haveRead; // ### error checking here } return bytes; } qint64 QHttpNetworkReplyPrivate::getChunkSize(QAbstractSocket *socket, qint64 *chunkSize) { qint64 bytes = 0; char crlf[2]; *chunkSize = -1; int bytesAvailable = socket->bytesAvailable(); // FIXME rewrite to permanent loop without bytesAvailable while (bytesAvailable > bytes) { qint64 sniffedBytes = socket->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 += socket->read(crlf, 1); // read the \r or \n if (crlf[0] == '\r') bytes += socket->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; qint64 haveRead = socket->read(&c, 1); if (haveRead < 0) { return -1; // FIXME } bytes += haveRead; fragment.append(c); } } return bytes; } bool QHttpNetworkReplyPrivate::isRedirecting() const { // We're in the process of redirecting - if the HTTP status code says so and // followRedirect is switched on return (QHttpNetworkReply::isHttpRedirect(parser.getStatusCode()) && request.isFollowRedirects()); } bool QHttpNetworkReplyPrivate::shouldEmitSignals() { // for 401 & 407 don't emit the data signals. Content along with these // responses are sent only if the authentication fails. return parser.getStatusCode() != 401 && parser.getStatusCode() != 407; } bool QHttpNetworkReplyPrivate::expectContent() { int statusCode = parser.getStatusCode(); // check whether we can expect content after the headers (rfc 2616, sec4.4) if ((statusCode >= 100 && statusCode < 200) || statusCode == 204 || statusCode == 304) return false; if (request.operation() == QHttpNetworkRequest::Head) return false; // no body expected for HEAD request qint64 expectedContentLength = contentLength(); if (expectedContentLength == 0) return false; if (expectedContentLength == -1 && bodyLength == 0) { // The content-length header was stripped, but its value was 0. // This would be the case for an explicitly zero-length compressed response. return false; } return true; } void QHttpNetworkReplyPrivate::eraseData() { responseData.clear(); } // SSL support below #ifndef QT_NO_SSL QSslConfiguration QHttpNetworkReply::sslConfiguration() const { Q_D(const QHttpNetworkReply); if (!d->connectionChannel) return QSslConfiguration(); QSslSocket *sslSocket = qobject_cast(d->connectionChannel->socket); if (!sslSocket) return QSslConfiguration(); return sslSocket->sslConfiguration(); } 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(); } void QHttpNetworkReply::ignoreSslErrors(const QList &errors) { Q_D(QHttpNetworkReply); if (d->connection) d->connection->ignoreSslErrors(errors); } #endif //QT_NO_SSL QT_END_NAMESPACE