mirror of
https://bitbucket.org/smil3y/katie.git
synced 2025-02-26 20:03:13 +00:00
1219 lines
50 KiB
C++
1219 lines
50 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2015 The Qt Company Ltd.
|
|
** Contact: http://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 http://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at http://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 2.1 or version 3 as published by the Free
|
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
|
** following information to ensure the GNU Lesser General Public License
|
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** As a special exception, The Qt Company gives you certain additional
|
|
** rights. These rights are described in The Qt Company LGPL Exception
|
|
** version 1.1, 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.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qhttpnetworkconnection_p.h"
|
|
#include "qhttpnetworkconnectionchannel_p.h"
|
|
#include "qnoncontiguousbytedevice_p.h"
|
|
#include <QtNetwork/qsslkey.h>
|
|
#include <QtNetwork/qsslcipher.h>
|
|
#include <QtNetwork/qsslconfiguration.h>
|
|
#include <qpair.h>
|
|
#include <qdebug.h>
|
|
|
|
#ifndef QT_NO_HTTP
|
|
|
|
#ifndef QT_NO_BEARERMANAGEMENT
|
|
#include "qnetworksession_p.h"
|
|
#endif
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
// TODO: Put channel specific stuff here so it does not polute qhttpnetworkconnection.cpp
|
|
|
|
QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel()
|
|
: socket(0)
|
|
, ssl(false)
|
|
, state(IdleState)
|
|
, reply(0)
|
|
, written(0)
|
|
, bytesTotal(0)
|
|
, resendCurrent(false)
|
|
, lastStatus(0)
|
|
, pendingEncrypt(false)
|
|
, reconnectAttempts(2)
|
|
, authMethod(QAuthenticatorPrivate::None)
|
|
, proxyAuthMethod(QAuthenticatorPrivate::None)
|
|
, authenticationCredentialsSent(false)
|
|
, proxyCredentialsSent(false)
|
|
, ignoreAllSslErrors(false)
|
|
, pipeliningSupported(PipeliningSupportUnknown)
|
|
, connection(0)
|
|
{
|
|
// Inlining this function in the header leads to compiler error on
|
|
// release-armv5, on at least timebox 9.2 and 10.1.
|
|
}
|
|
|
|
void QHttpNetworkConnectionChannel::init()
|
|
{
|
|
if (connection->d_func()->encrypt)
|
|
socket = new QSslSocket;
|
|
else
|
|
socket = new QTcpSocket;
|
|
#ifndef QT_NO_BEARERMANAGEMENT
|
|
//push session down to socket
|
|
if (networkSession)
|
|
socket->setProperty("_q_networksession", QVariant::fromValue(networkSession));
|
|
#endif
|
|
#ifndef QT_NO_NETWORKPROXY
|
|
// Set by QNAM anyway, but let's be safe here
|
|
socket->setProxy(QNetworkProxy::NoProxy);
|
|
#endif
|
|
|
|
// We want all signals (except the interactive ones) be connected as QueuedConnection
|
|
// because else we're falling into cases where we recurse back into the socket code
|
|
// and mess up the state. Always going to the event loop (and expecting that when reading/writing)
|
|
// is safer.
|
|
QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
|
|
this, SLOT(_q_bytesWritten(qint64)),
|
|
Qt::QueuedConnection);
|
|
QObject::connect(socket, SIGNAL(connected()),
|
|
this, SLOT(_q_connected()),
|
|
Qt::QueuedConnection);
|
|
QObject::connect(socket, SIGNAL(readyRead()),
|
|
this, SLOT(_q_readyRead()),
|
|
Qt::QueuedConnection);
|
|
|
|
// The disconnected() and error() signals may already come
|
|
// while calling connectToHost().
|
|
// In case of a cached hostname or an IP this
|
|
// will then emit a signal to the user of QNetworkReply
|
|
// but cannot be caught because the user did not have a chance yet
|
|
// to connect to QNetworkReply's signals.
|
|
qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");
|
|
QObject::connect(socket, SIGNAL(disconnected()),
|
|
this, SLOT(_q_disconnected()),
|
|
Qt::QueuedConnection);
|
|
QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
|
|
this, SLOT(_q_error(QAbstractSocket::SocketError)),
|
|
Qt::QueuedConnection);
|
|
|
|
|
|
#ifndef QT_NO_NETWORKPROXY
|
|
QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
|
|
this, SLOT(_q_proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
|
|
Qt::DirectConnection);
|
|
#endif
|
|
|
|
QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
|
|
if (sslSocket) {
|
|
// won't be a sslSocket if encrypt is false
|
|
QObject::connect(sslSocket, SIGNAL(encrypted()),
|
|
this, SLOT(_q_encrypted()),
|
|
Qt::QueuedConnection);
|
|
QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
|
|
this, SLOT(_q_sslErrors(QList<QSslError>)),
|
|
Qt::DirectConnection);
|
|
QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
|
|
this, SLOT(_q_encryptedBytesWritten(qint64)),
|
|
Qt::QueuedConnection);
|
|
}
|
|
}
|
|
|
|
|
|
void QHttpNetworkConnectionChannel::close()
|
|
{
|
|
if (socket->state() == QAbstractSocket::UnconnectedState)
|
|
state = QHttpNetworkConnectionChannel::IdleState;
|
|
else
|
|
state = QHttpNetworkConnectionChannel::ClosingState;
|
|
|
|
if (socket)
|
|
socket->close();
|
|
}
|
|
|
|
|
|
bool QHttpNetworkConnectionChannel::sendRequest()
|
|
{
|
|
if (!reply) {
|
|
// heh, how should that happen!
|
|
qWarning() << "QHttpNetworkConnectionChannel::sendRequest() called without QHttpNetworkReply";
|
|
state = QHttpNetworkConnectionChannel::IdleState;
|
|
return false;
|
|
}
|
|
|
|
switch (state) {
|
|
case QHttpNetworkConnectionChannel::IdleState: { // write the header
|
|
if (!ensureConnection()) {
|
|
// wait for the connection (and encryption) to be done
|
|
// sendRequest will be called again from either
|
|
// _q_connected or _q_encrypted
|
|
return false;
|
|
}
|
|
written = 0; // excluding the header
|
|
bytesTotal = 0;
|
|
|
|
QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
|
|
replyPrivate->clear();
|
|
replyPrivate->connection = connection;
|
|
replyPrivate->connectionChannel = this;
|
|
replyPrivate->autoDecompress = request.d->autoDecompress;
|
|
replyPrivate->pipeliningUsed = false;
|
|
|
|
// if the url contains authentication parameters, use the new ones
|
|
// both channels will use the new authentication parameters
|
|
if (!request.url().userInfo().isEmpty() && request.withCredentials()) {
|
|
QUrl url = request.url();
|
|
QAuthenticator &auth = authenticator;
|
|
if (url.userName() != auth.user()
|
|
|| (!url.password().isEmpty() && url.password() != auth.password())) {
|
|
auth.setUser(url.userName());
|
|
auth.setPassword(url.password());
|
|
connection->d_func()->copyCredentials(connection->d_func()->indexOf(socket), &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());
|
|
request.setUrl(url);
|
|
}
|
|
// Will only be false if QtWebKit is performing a cross-origin XMLHttpRequest
|
|
// and withCredentials has not been set to true.
|
|
if (request.withCredentials())
|
|
connection->d_func()->createAuthorization(socket, request);
|
|
#ifndef QT_NO_NETWORKPROXY
|
|
QByteArray header = QHttpNetworkRequestPrivate::header(request,
|
|
(connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy));
|
|
#else
|
|
QByteArray header = QHttpNetworkRequestPrivate::header(request, false);
|
|
#endif
|
|
socket->write(header);
|
|
// flushing is dangerous (QSslSocket calls transmit which might read or error)
|
|
// socket->flush();
|
|
QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
|
|
if (uploadByteDevice) {
|
|
// connect the signals so this function gets called again
|
|
QObject::connect(uploadByteDevice, SIGNAL(readyRead()),this, SLOT(_q_uploadDataReadyRead()));
|
|
|
|
bytesTotal = request.contentLength();
|
|
|
|
state = QHttpNetworkConnectionChannel::WritingState; // start writing data
|
|
sendRequest(); //recurse
|
|
} else {
|
|
state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
|
|
sendRequest(); //recurse
|
|
}
|
|
|
|
break;
|
|
}
|
|
case QHttpNetworkConnectionChannel::WritingState:
|
|
{
|
|
// write the data
|
|
QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
|
|
if (!uploadByteDevice || bytesTotal == written) {
|
|
if (uploadByteDevice)
|
|
emit reply->dataSendProgress(written, bytesTotal);
|
|
state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
|
|
sendRequest(); // recurse
|
|
break;
|
|
}
|
|
|
|
// only feed the QTcpSocket buffer when there is less than 32 kB in it
|
|
const qint64 socketBufferFill = 32*1024;
|
|
const qint64 socketWriteMaxSize = 16*1024;
|
|
|
|
|
|
QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
|
|
// if it is really an ssl socket, check more than just bytesToWrite()
|
|
while ((socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : 0))
|
|
<= socketBufferFill && bytesTotal != written) {
|
|
// get pointer to upload data
|
|
qint64 currentReadSize = 0;
|
|
qint64 desiredReadSize = qMin(socketWriteMaxSize, bytesTotal - written);
|
|
const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize);
|
|
|
|
if (currentReadSize == -1) {
|
|
// premature eof happened
|
|
connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
|
|
return false;
|
|
break;
|
|
} else if (readPointer == 0 || currentReadSize == 0) {
|
|
// nothing to read currently, break the loop
|
|
break;
|
|
} else {
|
|
if (written != uploadByteDevice->pos()) {
|
|
// Sanity check. This was useful in tracking down an upload corruption.
|
|
qWarning() << "QHttpProtocolHandler: Internal error in sendRequest. Expected to write at position" << written << "but read device is at" << uploadByteDevice->pos();
|
|
Q_ASSERT(written == uploadByteDevice->pos());
|
|
connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolFailure);
|
|
return false;
|
|
}
|
|
|
|
qint64 currentWriteSize = socket->write(readPointer, currentReadSize);
|
|
if (currentWriteSize == -1 || currentWriteSize != currentReadSize) {
|
|
// socket broke down
|
|
connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
|
|
return false;
|
|
} else {
|
|
written += currentWriteSize;
|
|
uploadByteDevice->advanceReadPointer(currentWriteSize);
|
|
|
|
emit reply->dataSendProgress(written, bytesTotal);
|
|
|
|
if (written == bytesTotal) {
|
|
// make sure this function is called once again
|
|
state = QHttpNetworkConnectionChannel::WaitingState;
|
|
sendRequest();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case QHttpNetworkConnectionChannel::WaitingState:
|
|
{
|
|
QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
|
|
if (uploadByteDevice) {
|
|
QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(_q_uploadDataReadyRead()));
|
|
}
|
|
|
|
// HTTP pipelining
|
|
//connection->d_func()->fillPipeline(socket);
|
|
//socket->flush();
|
|
|
|
// ensure we try to receive a reply in all cases, even if _q_readyRead_ hat not been called
|
|
// this is needed if the sends an reply before we have finished sending the request. In that
|
|
// case receiveReply had been called before but ignored the server reply
|
|
if (socket->bytesAvailable())
|
|
QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
|
|
break;
|
|
}
|
|
case QHttpNetworkConnectionChannel::ReadingState:
|
|
// ignore _q_bytesWritten in these states
|
|
// fall through
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void QHttpNetworkConnectionChannel::_q_receiveReply()
|
|
{
|
|
Q_ASSERT(socket);
|
|
|
|
if (!reply) {
|
|
if (socket->bytesAvailable() > 0)
|
|
qWarning() << "QHttpNetworkConnectionChannel::_q_receiveReply() called without QHttpNetworkReply,"
|
|
<< socket->bytesAvailable() << "bytes on socket.";
|
|
|
|
close();
|
|
return;
|
|
}
|
|
|
|
// only run when the QHttpNetworkConnection is not currently being destructed, e.g.
|
|
// this function is called from _q_disconnected which is called because
|
|
// of ~QHttpNetworkConnectionPrivate
|
|
if (!qobject_cast<QHttpNetworkConnection*>(connection)) {
|
|
return;
|
|
}
|
|
|
|
QAbstractSocket::SocketState socketState = socket->state();
|
|
|
|
// connection might be closed to signal the end of data
|
|
if (socketState == QAbstractSocket::UnconnectedState) {
|
|
if (socket->bytesAvailable() <= 0) {
|
|
if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
|
|
// finish this reply. this case happens when the server did not send a content length
|
|
reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
|
|
allDone();
|
|
return;
|
|
} else {
|
|
handleUnexpectedEOF();
|
|
return;
|
|
}
|
|
} else {
|
|
// socket not connected but still bytes for reading.. just continue in this function
|
|
}
|
|
}
|
|
|
|
// read loop for the response
|
|
qint64 bytes = 0;
|
|
qint64 lastBytes = bytes;
|
|
do {
|
|
lastBytes = bytes;
|
|
|
|
QHttpNetworkReplyPrivate::ReplyState state = reply->d_func()->state;
|
|
switch (state) {
|
|
case QHttpNetworkReplyPrivate::NothingDoneState: {
|
|
state = reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState;
|
|
// fallthrough
|
|
}
|
|
case QHttpNetworkReplyPrivate::ReadingStatusState: {
|
|
qint64 statusBytes = reply->d_func()->readStatus(socket);
|
|
if (statusBytes == -1) {
|
|
// connection broke while reading status. also handled if later _q_disconnected is called
|
|
handleUnexpectedEOF();
|
|
return;
|
|
}
|
|
bytes += statusBytes;
|
|
lastStatus = reply->d_func()->statusCode;
|
|
break;
|
|
}
|
|
case QHttpNetworkReplyPrivate::ReadingHeaderState: {
|
|
QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
|
|
qint64 headerBytes = replyPrivate->readHeader(socket);
|
|
if (headerBytes == -1) {
|
|
// connection broke while reading headers. also handled if later _q_disconnected is called
|
|
handleUnexpectedEOF();
|
|
return;
|
|
}
|
|
bytes += headerBytes;
|
|
// If headers were parsed successfully now it is the ReadingDataState
|
|
if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) {
|
|
if (replyPrivate->isGzipped() && replyPrivate->autoDecompress) {
|
|
// remove the Content-Length from header
|
|
replyPrivate->removeAutoDecompressHeader();
|
|
} else {
|
|
replyPrivate->autoDecompress = false;
|
|
}
|
|
if (replyPrivate->statusCode == 100) {
|
|
replyPrivate->clearHttpLayerInformation();
|
|
replyPrivate->state = QHttpNetworkReplyPrivate::ReadingStatusState;
|
|
break; // ignore
|
|
}
|
|
if (replyPrivate->shouldEmitSignals())
|
|
emit reply->headerChanged();
|
|
// After headerChanged had been emitted
|
|
// we can suddenly have a replyPrivate->userProvidedDownloadBuffer
|
|
// this is handled in the ReadingDataState however
|
|
|
|
if (!replyPrivate->expectContent()) {
|
|
replyPrivate->state = QHttpNetworkReplyPrivate::AllDoneState;
|
|
allDone();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case QHttpNetworkReplyPrivate::ReadingDataState: {
|
|
QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
|
|
if (socket->state() == QAbstractSocket::ConnectedState &&
|
|
replyPrivate->downstreamLimited && !replyPrivate->responseData.isEmpty() && replyPrivate->shouldEmitSignals()) {
|
|
// (only do the following when still connected, not when we have already been disconnected and there is still data)
|
|
// We already have some HTTP body data. We don't read more from the socket until
|
|
// this is fetched by QHttpNetworkAccessHttpBackend. If we would read more,
|
|
// we could not limit our read buffer usage.
|
|
// We only do this when shouldEmitSignals==true because our HTTP parsing
|
|
// always needs to parse the 401/407 replies. Therefore they don't really obey
|
|
// to the read buffer maximum size, but we don't care since they should be small.
|
|
return;
|
|
}
|
|
|
|
if (replyPrivate->userProvidedDownloadBuffer) {
|
|
// the user provided a direct buffer where we should put all our data in.
|
|
// this only works when we can tell the user the content length and he/she can allocate
|
|
// the buffer in that size.
|
|
// note that this call will read only from the still buffered data
|
|
qint64 haveRead = replyPrivate->readBodyVeryFast(socket, replyPrivate->userProvidedDownloadBuffer + replyPrivate->totalProgress);
|
|
bytes += haveRead;
|
|
replyPrivate->totalProgress += haveRead;
|
|
|
|
// the user will get notified of it via progress signal
|
|
if (haveRead > 0)
|
|
emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
|
|
} else if (!replyPrivate->isChunked() && !replyPrivate->autoDecompress
|
|
&& replyPrivate->bodyLength > 0) {
|
|
// bulk files like images should fulfill these properties and
|
|
// we can therefore save on memory copying
|
|
qint64 haveRead = replyPrivate->readBodyFast(socket, &replyPrivate->responseData);
|
|
bytes += haveRead;
|
|
replyPrivate->totalProgress += haveRead;
|
|
if (replyPrivate->shouldEmitSignals()) {
|
|
emit reply->readyRead();
|
|
emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// use the traditional slower reading (for compressed encoding, chunked encoding,
|
|
// no content-length etc)
|
|
QByteDataBuffer byteDatas;
|
|
qint64 haveRead = replyPrivate->readBody(socket, &byteDatas);
|
|
if (haveRead) {
|
|
bytes += haveRead;
|
|
if (replyPrivate->autoDecompress)
|
|
replyPrivate->appendCompressedReplyData(byteDatas);
|
|
else
|
|
replyPrivate->appendUncompressedReplyData(byteDatas);
|
|
|
|
if (!replyPrivate->autoDecompress) {
|
|
replyPrivate->totalProgress += bytes;
|
|
if (replyPrivate->shouldEmitSignals()) {
|
|
// important: At the point of this readyRead(), the byteDatas list must be empty,
|
|
// else implicit sharing will trigger memcpy when the user is reading data!
|
|
emit reply->readyRead();
|
|
emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
|
|
}
|
|
}
|
|
#ifndef QT_NO_COMPRESS
|
|
else if (!expand(false)) { // expand a chunk if possible
|
|
// If expand() failed we can just return, it had already called connection->emitReplyError()
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
// still in ReadingDataState? This function will be called again by the socket's readyRead
|
|
if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState)
|
|
break;
|
|
|
|
// everything done, fall through
|
|
}
|
|
case QHttpNetworkReplyPrivate::AllDoneState:
|
|
allDone();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} while (bytes != lastBytes && reply);
|
|
}
|
|
|
|
// called when unexpectedly reading a -1 or when data is expected but socket is closed
|
|
void QHttpNetworkConnectionChannel::handleUnexpectedEOF()
|
|
{
|
|
Q_ASSERT(reply);
|
|
if (reconnectAttempts <= 0) {
|
|
// too many errors reading/receiving/parsing the status, close the socket and emit error
|
|
requeueCurrentlyPipelinedRequests();
|
|
close();
|
|
reply->d_func()->errorString = connection->d_func()->errorDetail(QNetworkReply::RemoteHostClosedError, socket);
|
|
emit reply->finishedWithError(QNetworkReply::RemoteHostClosedError, reply->d_func()->errorString);
|
|
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
|
} else {
|
|
reconnectAttempts--;
|
|
reply->d_func()->clear();
|
|
reply->d_func()->connection = connection;
|
|
reply->d_func()->connectionChannel = this;
|
|
closeAndResendCurrentRequest();
|
|
}
|
|
}
|
|
|
|
bool QHttpNetworkConnectionChannel::ensureConnection()
|
|
{
|
|
QAbstractSocket::SocketState socketState = socket->state();
|
|
|
|
// resend this request after we receive the disconnected signal
|
|
if (socketState == QAbstractSocket::ClosingState) {
|
|
if (reply)
|
|
resendCurrent = true;
|
|
return false;
|
|
}
|
|
|
|
// already trying to connect?
|
|
if (socketState == QAbstractSocket::HostLookupState ||
|
|
socketState == QAbstractSocket::ConnectingState) {
|
|
return false;
|
|
}
|
|
|
|
// make sure that this socket is in a connected state, if not initiate
|
|
// connection to the host.
|
|
if (socketState != QAbstractSocket::ConnectedState) {
|
|
// connect to the host if not already connected.
|
|
state = QHttpNetworkConnectionChannel::ConnectingState;
|
|
pendingEncrypt = ssl;
|
|
|
|
// reset state
|
|
pipeliningSupported = PipeliningSupportUnknown;
|
|
authenticationCredentialsSent = false;
|
|
proxyCredentialsSent = false;
|
|
authenticator.detach();
|
|
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(authenticator);
|
|
priv->hasFailed = false;
|
|
proxyAuthenticator.detach();
|
|
priv = QAuthenticatorPrivate::getPrivate(proxyAuthenticator);
|
|
priv->hasFailed = false;
|
|
|
|
// 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.
|
|
priv = QAuthenticatorPrivate::getPrivate(authenticator);
|
|
if (priv && priv->phase == QAuthenticatorPrivate::Done)
|
|
priv->phase = QAuthenticatorPrivate::Start;
|
|
priv = QAuthenticatorPrivate::getPrivate(proxyAuthenticator);
|
|
if (priv && priv->phase == QAuthenticatorPrivate::Done)
|
|
priv->phase = QAuthenticatorPrivate::Start;
|
|
|
|
QString connectHost = connection->d_func()->hostName;
|
|
qint16 connectPort = connection->d_func()->port;
|
|
|
|
#ifndef QT_NO_NETWORKPROXY
|
|
// HTTPS always use transparent proxy.
|
|
if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !ssl) {
|
|
connectHost = connection->d_func()->networkProxy.hostName();
|
|
connectPort = connection->d_func()->networkProxy.port();
|
|
}
|
|
if (socket->proxy().type() == QNetworkProxy::HttpProxy) {
|
|
// Make user-agent field available to HTTP proxy socket engine (QTBUG-17223)
|
|
QByteArray value;
|
|
// ensureConnection is called before any request has been assigned, but can also be called again if reconnecting
|
|
if (request.url().isEmpty())
|
|
value = connection->d_func()->predictNextRequest().headerField("user-agent");
|
|
else
|
|
value = request.headerField("user-agent");
|
|
if (!value.isEmpty())
|
|
socket->setProperty("_q_user-agent", value);
|
|
}
|
|
#endif
|
|
if (ssl) {
|
|
QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
|
|
sslSocket->connectToHostEncrypted(connectHost, connectPort);
|
|
if (ignoreAllSslErrors)
|
|
sslSocket->ignoreSslErrors();
|
|
sslSocket->ignoreSslErrors(ignoreSslErrorsList);
|
|
|
|
// limit the socket read buffer size. we will read everything into
|
|
// the QHttpNetworkReply anyway, so let's grow only that and not
|
|
// here and there.
|
|
socket->setReadBufferSize(64*1024);
|
|
} else {
|
|
// In case of no proxy we can use the Unbuffered QTcpSocket
|
|
#ifndef QT_NO_NETWORKPROXY
|
|
if (connection->d_func()->networkProxy.type() == QNetworkProxy::NoProxy
|
|
&& connection->cacheProxy().type() == QNetworkProxy::NoProxy
|
|
&& connection->transparentProxy().type() == QNetworkProxy::NoProxy) {
|
|
#endif
|
|
socket->connectToHost(connectHost, connectPort, QIODevice::ReadWrite | QIODevice::Unbuffered);
|
|
// For an Unbuffered QTcpSocket, the read buffer size has a special meaning.
|
|
socket->setReadBufferSize(1*1024);
|
|
#ifndef QT_NO_NETWORKPROXY
|
|
} else {
|
|
socket->connectToHost(connectHost, connectPort);
|
|
|
|
// limit the socket read buffer size. we will read everything into
|
|
// the QHttpNetworkReply anyway, so let's grow only that and not
|
|
// here and there.
|
|
socket->setReadBufferSize(64*1024);
|
|
}
|
|
#endif
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// This code path for ConnectedState
|
|
if (pendingEncrypt) {
|
|
// Let's only be really connected when we have received the encrypted() signal. Else the state machine seems to mess up
|
|
// and corrupt the things sent to the server.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
#ifndef QT_NO_COMPRESS
|
|
bool QHttpNetworkConnectionChannel::expand(bool dataComplete)
|
|
{
|
|
Q_ASSERT(socket);
|
|
Q_ASSERT(reply);
|
|
|
|
qint64 total = reply->d_func()->compressedData.size();
|
|
if (total >= CHUNK || dataComplete) {
|
|
// uncompress the data
|
|
QByteArray content, inflated;
|
|
content = reply->d_func()->compressedData;
|
|
reply->d_func()->compressedData.clear();
|
|
|
|
int ret = Z_OK;
|
|
if (content.size())
|
|
ret = reply->d_func()->gunzipBodyPartially(content, inflated);
|
|
if (ret >= Z_OK) {
|
|
if (dataComplete && ret == Z_OK && !reply->d_func()->streamEnd) {
|
|
reply->d_func()->gunzipBodyPartiallyEnd();
|
|
reply->d_func()->streamEnd = true;
|
|
}
|
|
if (inflated.size()) {
|
|
reply->d_func()->totalProgress += inflated.size();
|
|
reply->d_func()->appendUncompressedReplyData(inflated);
|
|
if (reply->d_func()->shouldEmitSignals()) {
|
|
// important: At the point of this readyRead(), inflated must be cleared,
|
|
// else implicit sharing will trigger memcpy when the user is reading data!
|
|
emit reply->readyRead();
|
|
emit reply->dataReadProgress(reply->d_func()->totalProgress, 0);
|
|
}
|
|
}
|
|
} else {
|
|
connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolFailure);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
void QHttpNetworkConnectionChannel::allDone()
|
|
{
|
|
Q_ASSERT(reply);
|
|
#ifndef QT_NO_COMPRESS
|
|
// expand the whole data.
|
|
if (reply->d_func()->expectContent() && reply->d_func()->autoDecompress && !reply->d_func()->streamEnd) {
|
|
bool expandResult = expand(true);
|
|
// If expand() failed we can just return, it had already called connection->emitReplyError()
|
|
if (!expandResult)
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (!reply) {
|
|
qWarning() << "QHttpNetworkConnectionChannel::allDone() called without reply. Please report at http://bugreports.qt-project.org/";
|
|
return;
|
|
}
|
|
|
|
// while handling 401 & 407, we might reset the status code, so save this.
|
|
bool emitFinished = reply->d_func()->shouldEmitSignals();
|
|
bool connectionCloseEnabled = reply->d_func()->isConnectionCloseEnabled();
|
|
detectPipeliningSupport();
|
|
|
|
handleStatus();
|
|
// handleStatus() might have removed the reply because it already called connection->emitReplyError()
|
|
|
|
// 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 (reply && 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.
|
|
reconnectAttempts = 2;
|
|
|
|
// now the channel can be seen as free/idle again, all signal emissions for the reply have been done
|
|
if (state != QHttpNetworkConnectionChannel::ClosingState)
|
|
state = QHttpNetworkConnectionChannel::IdleState;
|
|
|
|
// if it does not need to be sent again we can set it to 0
|
|
// the previous code did not do that and we had problems with accidental re-sending of a
|
|
// finished request.
|
|
// Note that this may trigger a segfault at some other point. But then we can fix the underlying
|
|
// problem.
|
|
if (!resendCurrent) {
|
|
request = QHttpNetworkRequest();
|
|
reply = 0;
|
|
}
|
|
|
|
// move next from pipeline to current request
|
|
if (!alreadyPipelinedRequests.isEmpty()) {
|
|
if (resendCurrent || connectionCloseEnabled || socket->state() != QAbstractSocket::ConnectedState) {
|
|
// move the pipelined ones back to the main queue
|
|
requeueCurrentlyPipelinedRequests();
|
|
close();
|
|
} else {
|
|
// there were requests pipelined in and we can continue
|
|
HttpMessagePair messagePair = alreadyPipelinedRequests.takeFirst();
|
|
|
|
request = messagePair.first;
|
|
reply = messagePair.second;
|
|
state = QHttpNetworkConnectionChannel::ReadingState;
|
|
resendCurrent = false;
|
|
|
|
written = 0; // message body, excluding the header, irrelevant here
|
|
bytesTotal = 0; // message body total, excluding the header, irrelevant here
|
|
|
|
// pipeline even more
|
|
connection->d_func()->fillPipeline(socket);
|
|
|
|
// continue reading
|
|
//_q_receiveReply();
|
|
// this was wrong, allDone gets called from that function anyway.
|
|
}
|
|
} else if (alreadyPipelinedRequests.isEmpty() && socket->bytesAvailable() > 0) {
|
|
// this is weird. we had nothing pipelined but still bytes available. better close it.
|
|
close();
|
|
|
|
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
|
} else if (alreadyPipelinedRequests.isEmpty()) {
|
|
if (connectionCloseEnabled)
|
|
if (socket->state() != QAbstractSocket::UnconnectedState)
|
|
close();
|
|
if (qobject_cast<QHttpNetworkConnection*>(connection))
|
|
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
|
}
|
|
}
|
|
|
|
void QHttpNetworkConnectionChannel::detectPipeliningSupport()
|
|
{
|
|
Q_ASSERT(reply);
|
|
// detect HTTP Pipelining support
|
|
QByteArray serverHeaderField;
|
|
if (
|
|
// check for HTTP/1.1
|
|
(reply->d_func()->majorVersion == 1 && reply->d_func()->minorVersion == 1)
|
|
// check for not having connection close
|
|
&& (!reply->d_func()->isConnectionCloseEnabled())
|
|
// check if it is still connected
|
|
&& (socket->state() == QAbstractSocket::ConnectedState)
|
|
// check for broken servers in server reply header
|
|
// this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining
|
|
&& (serverHeaderField = reply->headerField("Server"), !serverHeaderField.contains("Microsoft-IIS/4."))
|
|
&& (!serverHeaderField.contains("Microsoft-IIS/5."))
|
|
&& (!serverHeaderField.contains("Netscape-Enterprise/3."))
|
|
// this is adpoted from the knowledge of the Nokia 7.x browser team (DEF143319)
|
|
&& (!serverHeaderField.contains("WebLogic"))
|
|
&& (!serverHeaderField.startsWith("Rocket")) // a Python Web Server, see Web2py.com
|
|
) {
|
|
pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningProbablySupported;
|
|
} else {
|
|
pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
|
|
}
|
|
}
|
|
|
|
// called when the connection broke and we need to queue some pipelined requests again
|
|
void QHttpNetworkConnectionChannel::requeueCurrentlyPipelinedRequests()
|
|
{
|
|
for (int i = 0; i < alreadyPipelinedRequests.length(); i++)
|
|
connection->d_func()->requeueRequest(alreadyPipelinedRequests.at(i));
|
|
alreadyPipelinedRequests.clear();
|
|
|
|
// only run when the QHttpNetworkConnection is not currently being destructed, e.g.
|
|
// this function is called from _q_disconnected which is called because
|
|
// of ~QHttpNetworkConnectionPrivate
|
|
if (qobject_cast<QHttpNetworkConnection*>(connection))
|
|
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
|
}
|
|
|
|
void QHttpNetworkConnectionChannel::handleStatus()
|
|
{
|
|
Q_ASSERT(socket);
|
|
Q_ASSERT(reply);
|
|
|
|
int statusCode = reply->statusCode();
|
|
bool resend = false;
|
|
|
|
switch (statusCode) {
|
|
case 401: // auth required
|
|
case 407: // proxy auth required
|
|
if (connection->d_func()->handleAuthenticateChallenge(socket, reply, (statusCode == 407), resend)) {
|
|
if (resend) {
|
|
if (!resetUploadData())
|
|
break;
|
|
|
|
reply->d_func()->eraseData();
|
|
|
|
if (alreadyPipelinedRequests.isEmpty()) {
|
|
// this does a re-send without closing the connection
|
|
resendCurrent = true;
|
|
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
|
} else {
|
|
// we had requests pipelined.. better close the connection in closeAndResendCurrentRequest
|
|
closeAndResendCurrentRequest();
|
|
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
|
}
|
|
} else {
|
|
//authentication cancelled, close the channel.
|
|
close();
|
|
}
|
|
} else {
|
|
emit reply->headerChanged();
|
|
emit reply->readyRead();
|
|
QNetworkReply::NetworkError errorCode = (statusCode == 407)
|
|
? QNetworkReply::ProxyAuthenticationRequiredError
|
|
: QNetworkReply::AuthenticationRequiredError;
|
|
reply->d_func()->errorString = connection->d_func()->errorDetail(errorCode, socket);
|
|
emit reply->finishedWithError(errorCode, reply->d_func()->errorString);
|
|
}
|
|
break;
|
|
default:
|
|
if (qobject_cast<QHttpNetworkConnection*>(connection))
|
|
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
|
}
|
|
}
|
|
|
|
bool QHttpNetworkConnectionChannel::resetUploadData()
|
|
{
|
|
if (!reply) {
|
|
//this happens if server closes connection while QHttpNetworkConnectionPrivate::_q_startNextRequest is pending
|
|
return false;
|
|
}
|
|
QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
|
|
if (!uploadByteDevice)
|
|
return true;
|
|
|
|
if (uploadByteDevice->reset()) {
|
|
written = 0;
|
|
return true;
|
|
} else {
|
|
connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ContentReSendError);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
void QHttpNetworkConnectionChannel::pipelineInto(HttpMessagePair &pair)
|
|
{
|
|
// this is only called for simple GET
|
|
|
|
QHttpNetworkRequest &request = pair.first;
|
|
QHttpNetworkReply *reply = pair.second;
|
|
reply->d_func()->clear();
|
|
reply->d_func()->connection = connection;
|
|
reply->d_func()->connectionChannel = this;
|
|
reply->d_func()->autoDecompress = request.d->autoDecompress;
|
|
reply->d_func()->pipeliningUsed = true;
|
|
|
|
#ifndef QT_NO_NETWORKPROXY
|
|
pipeline.append(QHttpNetworkRequestPrivate::header(request,
|
|
(connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy)));
|
|
#else
|
|
pipeline.append(QHttpNetworkRequestPrivate::header(request, false));
|
|
#endif
|
|
|
|
alreadyPipelinedRequests.append(pair);
|
|
|
|
// pipelineFlush() needs to be called at some point afterwards
|
|
}
|
|
|
|
void QHttpNetworkConnectionChannel::pipelineFlush()
|
|
{
|
|
if (pipeline.isEmpty())
|
|
return;
|
|
|
|
// The goal of this is so that we have everything in one TCP packet.
|
|
// For the Unbuffered QTcpSocket this is manually needed, the buffered
|
|
// QTcpSocket does it automatically.
|
|
// Also, sometimes the OS does it for us (Nagle's algorithm) but that
|
|
// happens only sometimes.
|
|
socket->write(pipeline);
|
|
pipeline.clear();
|
|
}
|
|
|
|
|
|
void QHttpNetworkConnectionChannel::closeAndResendCurrentRequest()
|
|
{
|
|
requeueCurrentlyPipelinedRequests();
|
|
close();
|
|
if (reply)
|
|
resendCurrent = true;
|
|
if (qobject_cast<QHttpNetworkConnection*>(connection))
|
|
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
|
}
|
|
|
|
bool QHttpNetworkConnectionChannel::isSocketBusy() const
|
|
{
|
|
return (state & QHttpNetworkConnectionChannel::BusyState);
|
|
}
|
|
|
|
bool QHttpNetworkConnectionChannel::isSocketWriting() const
|
|
{
|
|
return (state & QHttpNetworkConnectionChannel::WritingState);
|
|
}
|
|
|
|
bool QHttpNetworkConnectionChannel::isSocketWaiting() const
|
|
{
|
|
return (state & QHttpNetworkConnectionChannel::WaitingState);
|
|
}
|
|
|
|
bool QHttpNetworkConnectionChannel::isSocketReading() const
|
|
{
|
|
return (state & QHttpNetworkConnectionChannel::ReadingState);
|
|
}
|
|
|
|
//private slots
|
|
void QHttpNetworkConnectionChannel::_q_readyRead()
|
|
{
|
|
if (socket->state() == QAbstractSocket::ConnectedState && socket->bytesAvailable() == 0) {
|
|
// We got a readyRead but no bytes are available..
|
|
// This happens for the Unbuffered QTcpSocket
|
|
// Also check if socket is in ConnectedState since
|
|
// this function may also be invoked via the event loop.
|
|
char c;
|
|
qint64 ret = socket->peek(&c, 1);
|
|
if (ret < 0) {
|
|
_q_error(socket->error());
|
|
// We still need to handle the reply so it emits its signals etc.
|
|
if (reply)
|
|
_q_receiveReply();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (isSocketWaiting() || isSocketReading()) {
|
|
state = QHttpNetworkConnectionChannel::ReadingState;
|
|
if (reply)
|
|
_q_receiveReply();
|
|
}
|
|
}
|
|
|
|
void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes)
|
|
{
|
|
Q_UNUSED(bytes);
|
|
|
|
if (ssl) {
|
|
// In the SSL case we want to send data from encryptedBytesWritten signal since that one
|
|
// is the one going down to the actual network, not only into some SSL buffer.
|
|
return;
|
|
}
|
|
|
|
// bytes have been written to the socket. write even more of them :)
|
|
if (isSocketWriting())
|
|
sendRequest();
|
|
// otherwise we do nothing
|
|
}
|
|
|
|
void QHttpNetworkConnectionChannel::_q_disconnected()
|
|
{
|
|
if (state == QHttpNetworkConnectionChannel::ClosingState) {
|
|
state = QHttpNetworkConnectionChannel::IdleState;
|
|
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
|
return;
|
|
}
|
|
|
|
// read the available data before closing
|
|
if (isSocketWaiting() || isSocketReading()) {
|
|
if (reply) {
|
|
state = QHttpNetworkConnectionChannel::ReadingState;
|
|
_q_receiveReply();
|
|
}
|
|
} else if (state == QHttpNetworkConnectionChannel::IdleState && resendCurrent) {
|
|
// re-sending request because the socket was in ClosingState
|
|
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
|
}
|
|
state = QHttpNetworkConnectionChannel::IdleState;
|
|
|
|
requeueCurrentlyPipelinedRequests();
|
|
close();
|
|
}
|
|
|
|
|
|
void QHttpNetworkConnectionChannel::_q_connected()
|
|
{
|
|
// improve performance since we get the request sent by the kernel ASAP
|
|
//socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
|
|
// We have this commented out now. It did not have the effect we wanted. If we want to
|
|
// do this properly, Qt has to combine multiple HTTP requests into one buffer
|
|
// and send this to the kernel in one syscall and then the kernel immediately sends
|
|
// it as one TCP packet because of TCP_NODELAY.
|
|
// However, this code is currently not in Qt, so we rely on the kernel combining
|
|
// the requests into one TCP packet.
|
|
|
|
// not sure yet if it helps, but it makes sense
|
|
socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
|
|
|
|
pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
|
|
|
|
// ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
|
|
//channels[i].reconnectAttempts = 2;
|
|
if (!pendingEncrypt && !ssl) { // FIXME: Didn't work properly with pendingEncrypt only, we should refactor this into an EncrypingState
|
|
state = QHttpNetworkConnectionChannel::IdleState;
|
|
if (!reply)
|
|
connection->d_func()->dequeueRequest(socket);
|
|
if (reply)
|
|
sendRequest();
|
|
}
|
|
}
|
|
|
|
|
|
void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socketError)
|
|
{
|
|
if (!socket)
|
|
return;
|
|
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 (state != QHttpNetworkConnectionChannel::IdleState && state != QHttpNetworkConnectionChannel::ReadingState) {
|
|
if (reconnectAttempts-- > 0) {
|
|
closeAndResendCurrentRequest();
|
|
return;
|
|
} else {
|
|
errorCode = QNetworkReply::RemoteHostClosedError;
|
|
}
|
|
} else if (state == QHttpNetworkConnectionChannel::ReadingState) {
|
|
if (!reply)
|
|
break;
|
|
|
|
if (!reply->d_func()->expectContent()) {
|
|
// No content expected, this is a valid way to have the connection closed by the server
|
|
return;
|
|
}
|
|
if (reply->contentLength() == -1 && !reply->d_func()->isChunked()) {
|
|
// There was no content-length header and it's not chunked encoding,
|
|
// so this is a valid way to have the connection closed by the server
|
|
return;
|
|
}
|
|
// ok, we got a disconnect even though we did not expect it
|
|
// Try to read everything from the socket before we emit the error.
|
|
if (socket->bytesAvailable()) {
|
|
// Read everything from the socket into the reply buffer.
|
|
// we can ignore the readbuffersize as the data is already
|
|
// in memory and we will not recieve more data on the socket.
|
|
reply->setReadBufferSize(0);
|
|
_q_receiveReply();
|
|
if (ssl) {
|
|
// The QSslSocket can still have encrypted bytes in the plainsocket.
|
|
// So we need to check this if the socket is a QSslSocket. When the socket is flushed
|
|
// it will force a decrypt of the encrypted data in the plainsocket.
|
|
QSslSocket *sslSocket = static_cast<QSslSocket*>(socket);
|
|
qint64 beforeFlush = sslSocket->encryptedBytesAvailable();
|
|
while (sslSocket->encryptedBytesAvailable()) {
|
|
sslSocket->flush();
|
|
_q_receiveReply();
|
|
qint64 afterFlush = sslSocket->encryptedBytesAvailable();
|
|
if (afterFlush == beforeFlush)
|
|
break;
|
|
beforeFlush = afterFlush;
|
|
}
|
|
}
|
|
}
|
|
|
|
errorCode = QNetworkReply::RemoteHostClosedError;
|
|
} else {
|
|
errorCode = QNetworkReply::RemoteHostClosedError;
|
|
}
|
|
break;
|
|
case QAbstractSocket::SocketTimeoutError:
|
|
// try to reconnect/resend before sending an error.
|
|
if (state == QHttpNetworkConnectionChannel::WritingState && (reconnectAttempts-- > 0)) {
|
|
closeAndResendCurrentRequest();
|
|
return;
|
|
}
|
|
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<QHttpNetworkConnection> that = connection;
|
|
QString errorString = connection->d_func()->errorDetail(errorCode, socket, socket->errorString());
|
|
|
|
// Need to dequeu the request so that we can emit the error.
|
|
if (!reply)
|
|
connection->d_func()->dequeueRequest(socket);
|
|
if (reply) {
|
|
reply->d_func()->errorString = errorString;
|
|
emit reply->finishedWithError(errorCode, errorString);
|
|
reply = 0;
|
|
}
|
|
// send the next request
|
|
QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
|
|
|
|
if (that) //signal emission triggered event loop
|
|
close();
|
|
}
|
|
|
|
#ifndef QT_NO_NETWORKPROXY
|
|
void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth)
|
|
{
|
|
// Need to dequeue the request before we can emit the error.
|
|
if (!reply)
|
|
connection->d_func()->dequeueRequest(socket);
|
|
if (reply)
|
|
connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
|
|
}
|
|
#endif
|
|
|
|
void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead()
|
|
{
|
|
if (reply && state == QHttpNetworkConnectionChannel::WritingState) {
|
|
// There might be timing issues, make sure to only send upload data if really in that state
|
|
sendRequest();
|
|
}
|
|
}
|
|
|
|
void QHttpNetworkConnectionChannel::_q_encrypted()
|
|
{
|
|
if (!socket)
|
|
return; // ### error
|
|
state = QHttpNetworkConnectionChannel::IdleState;
|
|
pendingEncrypt = false;
|
|
if (!reply)
|
|
connection->d_func()->dequeueRequest(socket);
|
|
if (reply)
|
|
sendRequest();
|
|
}
|
|
|
|
void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
|
|
{
|
|
if (!socket)
|
|
return;
|
|
//QNetworkReply::NetworkError errorCode = QNetworkReply::ProtocolFailure;
|
|
// Also pause the connection because socket notifiers may fire while an user
|
|
// dialog is displaying
|
|
connection->d_func()->pauseConnection();
|
|
if (pendingEncrypt && !reply)
|
|
connection->d_func()->dequeueRequest(socket);
|
|
if (reply)
|
|
emit reply->sslErrors(errors);
|
|
connection->d_func()->resumeConnection();
|
|
}
|
|
|
|
void QHttpNetworkConnectionChannel::_q_encryptedBytesWritten(qint64 bytes)
|
|
{
|
|
Q_UNUSED(bytes);
|
|
// bytes have been written to the socket. write even more of them :)
|
|
if (isSocketWriting())
|
|
sendRequest();
|
|
// otherwise we do nothing
|
|
}
|
|
|
|
void QHttpNetworkConnectionChannel::setConnection(QHttpNetworkConnection *c)
|
|
{
|
|
// Inlining this function in the header leads to compiler error on
|
|
// release-armv5, on at least timebox 9.2 and 10.1.
|
|
connection = c;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
#include "moc_qhttpnetworkconnectionchannel_p.h"
|
|
|
|
#endif // QT_NO_HTTP
|
|
|
|
|