/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
** 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 "qhttpnetworkconnectionchannel_p.h"
#include "qhttpnetworkconnection_p.h"
#include "qhttp2configuration.h"
#include "private/qnoncontiguousbytedevice_p.h"

#include <qpair.h>
#include <qdebug.h>

#include <private/qhttp2protocolhandler_p.h>
#include <private/qhttpprotocolhandler_p.h>
#include <private/http2protocol_p.h>

#ifndef QT_NO_SSL
#    include <private/qsslsocket_p.h>
#    include <QtNetwork/qsslkey.h>
#    include <QtNetwork/qsslcipher.h>
#endif

#include "private/qnetconmonitor_p.h"

QT_BEGIN_NAMESPACE

namespace
{

class ProtocolHandlerDeleter : public QObject
{
public:
    explicit ProtocolHandlerDeleter(QAbstractProtocolHandler *h) : handler(h) {}
    ~ProtocolHandlerDeleter() { delete handler; }
private:
    QAbstractProtocolHandler *handler = nullptr;
};

}

// TODO: Put channel specific stuff here so it does not polute qhttpnetworkconnection.cpp

// Because in-flight when sending a request, the server might close our connection (because the persistent HTTP
// connection times out)
// We use 3 because we can get a _q_error 3 times depending on the timing:
static const int reconnectAttemptsDefault = 3;

QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel()
    : socket(nullptr)
    , ssl(false)
    , isInitialized(false)
    , state(IdleState)
    , reply(nullptr)
    , written(0)
    , bytesTotal(0)
    , resendCurrent(false)
    , lastStatus(0)
    , pendingEncrypt(false)
    , reconnectAttempts(reconnectAttemptsDefault)
    , authenticationCredentialsSent(false)
    , proxyCredentialsSent(false)
    , protocolHandler(nullptr)
#ifndef QT_NO_SSL
    , ignoreAllSslErrors(false)
#endif
    , pipeliningSupported(PipeliningSupportUnknown)
    , networkLayerPreference(QAbstractSocket::AnyIPProtocol)
    , connection(nullptr)
{
    // 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()
{
#ifndef QT_NO_SSL
    if (connection->d_func()->encrypt)
        socket = new QSslSocket;
    else
        socket = new QTcpSocket;
#else
    socket = new QTcpSocket;
#endif
#ifndef QT_NO_NETWORKPROXY
    // Set by QNAM anyway, but let's be safe here
    socket->setProxy(QNetworkProxy::NoProxy);
#endif

    // After some back and forth in all the last years, this is now a DirectConnection because otherwise
    // the state inside the *Socket classes gets messed up, also in conjunction with the socket notifiers
    // which behave slightly differently on Windows vs Linux
    QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
                     this, SLOT(_q_bytesWritten(qint64)),
                     Qt::DirectConnection);
    QObject::connect(socket, SIGNAL(connected()),
                     this, SLOT(_q_connected()),
                     Qt::DirectConnection);
    QObject::connect(socket, SIGNAL(readyRead()),
                     this, SLOT(_q_readyRead()),
                     Qt::DirectConnection);

    // 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>();
    QObject::connect(socket, SIGNAL(disconnected()),
                     this, SLOT(_q_disconnected()),
                     Qt::DirectConnection);
    QObject::connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)),
                     this, SLOT(_q_error(QAbstractSocket::SocketError)),
                     Qt::DirectConnection);


#ifndef QT_NO_NETWORKPROXY
    QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
                     this, SLOT(_q_proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
                     Qt::DirectConnection);
#endif

#ifndef QT_NO_SSL
    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::DirectConnection);
        QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
                         this, SLOT(_q_sslErrors(QList<QSslError>)),
                         Qt::DirectConnection);
        QObject::connect(sslSocket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
                         this, SLOT(_q_preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
                         Qt::DirectConnection);
        QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
                         this, SLOT(_q_encryptedBytesWritten(qint64)),
                         Qt::DirectConnection);

        if (ignoreAllSslErrors)
            sslSocket->ignoreSslErrors();

        if (!ignoreSslErrorsList.isEmpty())
            sslSocket->ignoreSslErrors(ignoreSslErrorsList);

        if (sslConfiguration.data() && !sslConfiguration->isNull())
           sslSocket->setSslConfiguration(*sslConfiguration);
    } else {
#endif // !QT_NO_SSL
        if (connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2)
            protocolHandler.reset(new QHttpProtocolHandler(this));
#ifndef QT_NO_SSL
    }
#endif

#ifndef QT_NO_NETWORKPROXY
    if (proxy.type() != QNetworkProxy::NoProxy)
        socket->setProxy(proxy);
#endif
    isInitialized = true;
}


void QHttpNetworkConnectionChannel::close()
{
    if (state == QHttpNetworkConnectionChannel::ClosingState)
        return;

    if (!socket)
        state = QHttpNetworkConnectionChannel::IdleState;
    else if (socket->state() == QAbstractSocket::UnconnectedState)
        state = QHttpNetworkConnectionChannel::IdleState;
    else
        state = QHttpNetworkConnectionChannel::ClosingState;

    // pendingEncrypt must only be true in between connected and encrypted states
    pendingEncrypt = false;

    if (socket) {
        // socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
        // there is no socket yet.
        socket->close();
    }
}


void QHttpNetworkConnectionChannel::abort()
{
    if (!socket)
        state = QHttpNetworkConnectionChannel::IdleState;
    else if (socket->state() == QAbstractSocket::UnconnectedState)
        state = QHttpNetworkConnectionChannel::IdleState;
    else
        state = QHttpNetworkConnectionChannel::ClosingState;

    // pendingEncrypt must only be true in between connected and encrypted states
    pendingEncrypt = false;

    if (socket) {
        // socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
        // there is no socket yet.
        socket->abort();
    }
}


bool QHttpNetworkConnectionChannel::sendRequest()
{
    Q_ASSERT(!protocolHandler.isNull());
    return protocolHandler->sendRequest();
}

/*
 * Invoke "protocolHandler->sendRequest" using a queued connection.
 * It's used to return to the event loop before invoking sendRequest when
 * there's a very real chance that the request could have been aborted
 * (i.e. after having emitted 'encrypted').
 */
void QHttpNetworkConnectionChannel::sendRequestDelayed()
{
    QMetaObject::invokeMethod(this, [this] {
        Q_ASSERT(!protocolHandler.isNull());
        if (reply)
            protocolHandler->sendRequest();
    }, Qt::ConnectionType::QueuedConnection);
}

void QHttpNetworkConnectionChannel::_q_receiveReply()
{
    Q_ASSERT(!protocolHandler.isNull());
    protocolHandler->_q_receiveReply();
}

void QHttpNetworkConnectionChannel::_q_readyRead()
{
    Q_ASSERT(!protocolHandler.isNull());
    protocolHandler->_q_readyRead();
}

// 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);
        reply = nullptr;
        if (protocolHandler)
            protocolHandler->setReply(nullptr);
        request = QHttpNetworkRequest();
        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()
{
    if (!isInitialized)
        init();

    QAbstractSocket::SocketState socketState = socket->state();

    // resend this request after we receive the disconnected signal
    // If !socket->isOpen() then we have already called close() on the socket, but there was still a
    // pending connectToHost() for which we hadn't seen a connected() signal, yet. The connected()
    // has now arrived (as indicated by socketState != ClosingState), but we cannot send anything on
    // such a socket anymore.
    if (socketState == QAbstractSocket::ClosingState ||
            (socketState != QAbstractSocket::UnconnectedState && !socket->isOpen())) {
        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;
        quint16 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()) {
                if (connection->connectionType()
                            == QHttpNetworkConnection::ConnectionTypeHTTP2Direct
                    || (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
                        && h2RequestsToSend.count() > 0)) {
                    value = h2RequestsToSend.first().first.headerField("user-agent");
                } else {
                    value = connection->d_func()->predictNextRequest().headerField("user-agent");
                }
            } else {
                value = request.headerField("user-agent");
            }
            if (!value.isEmpty()) {
                QNetworkProxy proxy(socket->proxy());
                proxy.setRawHeader("User-Agent", value); //detaches
                socket->setProxy(proxy);
            }
        }
#endif
        if (ssl) {
#ifndef QT_NO_SSL
            QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);

            // check whether we can re-use an existing SSL session
            // (meaning another socket in this connection has already
            // performed a full handshake)
            if (!connection->sslContext().isNull())
                QSslSocketPrivate::checkSettingSslContext(sslSocket, connection->sslContext());

            sslSocket->setPeerVerifyName(connection->d_func()->peerVerifyName);
            sslSocket->connectToHostEncrypted(connectHost, connectPort, QIODevice::ReadWrite, networkLayerPreference);
            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
            // Need to dequeue the request so that we can emit the error.
            if (!reply)
                connection->d_func()->dequeueRequest(socket);
            connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError);
#endif
        } 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, networkLayerPreference);
                // 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, QIODevice::ReadWrite, networkLayerPreference);

                // 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;
}

void QHttpNetworkConnectionChannel::allDone()
{
    Q_ASSERT(reply);

    if (!reply) {
        qWarning("QHttpNetworkConnectionChannel::allDone() called without reply. Please report at http://bugreports.qt.io/");
        return;
    }

    // For clear text HTTP/2 we tried to upgrade from HTTP/1.1 to HTTP/2; for
    // ConnectionTypeHTTP2Direct we can never be here in case of failure
    // (after an attempt to read HTTP/1.1 as HTTP/2 frames) or we have a normal
    // HTTP/2 response and thus can skip this test:
    if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
        && !ssl && !switchedToHttp2) {
        if (Http2::is_protocol_upgraded(*reply)) {
            switchedToHttp2 = true;
            protocolHandler->setReply(nullptr);

            // As allDone() gets called from the protocol handler, it's not yet
            // safe to delete it. There is no 'deleteLater', since
            // QAbstractProtocolHandler is not a QObject. Instead we do this
            // trick with ProtocolHandlerDeleter, a QObject-derived class.
            // These dances below just make it somewhat exception-safe.
            // 1. Create a new owner:
            QAbstractProtocolHandler *oldHandler = protocolHandler.data();
            QScopedPointer<ProtocolHandlerDeleter> deleter(new ProtocolHandlerDeleter(oldHandler));
            // 2. Retire the old one:
            protocolHandler.take();
            // 3. Call 'deleteLater':
            deleter->deleteLater();
            // 3. Give up the ownerthip:
            deleter.take();

            connection->fillHttp2Queue();
            protocolHandler.reset(new QHttp2ProtocolHandler(this));
            QHttp2ProtocolHandler *h2c = static_cast<QHttp2ProtocolHandler *>(protocolHandler.data());
            QMetaObject::invokeMethod(h2c, "_q_receiveReply", Qt::QueuedConnection);
            QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
            // If we only had one request sent with H2 allowed, we may fail to send
            // a client preface and SETTINGS, which is required by RFC 7540, 3.2.
            QMetaObject::invokeMethod(h2c, "ensureClientPrefaceSent", Qt::QueuedConnection);
            return;
        } else {
            // Ok, whatever happened, we do not try HTTP/2 anymore ...
            connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP);
            connection->d_func()->activeChannelCount = connection->d_func()->channelCount;
        }
    }

    // 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 = reconnectAttemptsDefault;

    // 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 = nullptr;
        protocolHandler->setReply(nullptr);
    }

    // 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;
            protocolHandler->setReply(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 301:
    case 302:
    case 303:
    case 305:
    case 307:
    case 308: {
        // Parse the response headers and get the "location" url
        QUrl redirectUrl = connection->d_func()->parseRedirectResponse(socket, reply);
        if (redirectUrl.isValid())
            reply->setRedirectUrl(redirectUrl);

        if ((statusCode == 307 || statusCode == 308) && !resetUploadData()) {
            // Couldn't reset the upload data, which means it will be unable to POST the data -
            // this would lead to a long wait until it eventually failed and then retried.
            // Instead of doing that we fail here instead, resetUploadData will already have emitted
            // a ContentReSendError, so we're done.
        } else if (qobject_cast<QHttpNetworkConnection *>(connection)) {
            QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
        }
        break;
    }
    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;
    }
    if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct
        || switchedToHttp2) {
        // The else branch doesn't make any sense for HTTP/2, since 1 channel is multiplexed into
        // many streams. And having one stream fail to reset upload data should not completely close
        // the channel. Handled in the http2 protocol handler.
    } else if (QNonContiguousByteDevice *uploadByteDevice = request.uploadByteDevice()) {
        if (!uploadByteDevice->reset()) {
            connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ContentReSendError);
            return false;
        }
        written = 0;
    }
    return true;
}

#ifndef QT_NO_NETWORKPROXY

void QHttpNetworkConnectionChannel::setProxy(const QNetworkProxy &networkProxy)
{
    if (socket)
        socket->setProxy(networkProxy);

    proxy = networkProxy;
}

#endif

#ifndef QT_NO_SSL

void QHttpNetworkConnectionChannel::ignoreSslErrors()
{
    if (socket)
        static_cast<QSslSocket *>(socket)->ignoreSslErrors();

    ignoreAllSslErrors = true;
}


void QHttpNetworkConnectionChannel::ignoreSslErrors(const QList<QSslError> &errors)
{
    if (socket)
        static_cast<QSslSocket *>(socket)->ignoreSslErrors(errors);

    ignoreSslErrorsList = errors;
}

void QHttpNetworkConnectionChannel::setSslConfiguration(const QSslConfiguration &config)
{
    if (socket)
        static_cast<QSslSocket *>(socket)->setSslConfiguration(config);

    if (sslConfiguration.data())
        *sslConfiguration = config;
    else
        sslConfiguration.reset(new QSslConfiguration(config));
}

#endif

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);
}

void QHttpNetworkConnectionChannel::resendCurrentRequest()
{
    requeueCurrentlyPipelinedRequests();
    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);
}

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 (also done in _q_error for other codepaths)
    if ((isSocketWaiting() || isSocketReading()) && socket->bytesAvailable()) {
        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;
    if (alreadyPipelinedRequests.length()) {
        // If nothing was in a pipeline, no need in calling
        // _q_startNextRequest (which it does):
        requeueCurrentlyPipelinedRequests();
    }

    pendingEncrypt = false;
}


void QHttpNetworkConnectionChannel::_q_connected()
{
    // For the Happy Eyeballs we need to check if this is the first channel to connect.
    if (connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::HostLookupPending || connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv4or6) {
        if (connection->d_func()->delayedConnectionTimer.isActive())
            connection->d_func()->delayedConnectionTimer.stop();
        if (networkLayerPreference == QAbstractSocket::IPv4Protocol)
            connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
        else if (networkLayerPreference == QAbstractSocket::IPv6Protocol)
            connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
        else {
            if (socket->peerAddress().protocol() == QAbstractSocket::IPv4Protocol)
                connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
            else
                connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
        }
        connection->d_func()->networkLayerDetected(networkLayerPreference);
    } else {
        bool anyProtocol = networkLayerPreference == QAbstractSocket::AnyIPProtocol;
        if (((connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv4)
             && (networkLayerPreference != QAbstractSocket::IPv4Protocol && !anyProtocol))
            || ((connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv6)
                && (networkLayerPreference != QAbstractSocket::IPv6Protocol && !anyProtocol))) {
            close();
            // This is the second connection so it has to be closed and we can schedule it for another request.
            QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
            return;
        }
        //The connections networkLayerState had already been decided.
    }

    // 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;

    if (QNetworkConnectionMonitor::isEnabled()) {
        auto connectionPrivate = connection->d_func();
        if (!connectionPrivate->connectionMonitor.isMonitoring()) {
            // Now that we have a pair of addresses, we can start monitoring the
            // connection status to handle its loss properly.
            if (connectionPrivate->connectionMonitor.setTargets(socket->localAddress(), socket->peerAddress()))
                connectionPrivate->connectionMonitor.startMonitoring();
        }
    }

    // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
    //channels[i].reconnectAttempts = 2;
    if (ssl || pendingEncrypt) { // FIXME: Didn't work properly with pendingEncrypt only, we should refactor this into an EncrypingState
#ifndef QT_NO_SSL
        if (connection->sslContext().isNull()) {
            // this socket is making the 1st handshake for this connection,
            // we need to set the SSL context so new sockets can reuse it
            QSharedPointer<QSslContext> socketSslContext = QSslSocketPrivate::sslContext(static_cast<QSslSocket*>(socket));
            if (!socketSslContext.isNull())
                connection->setSslContext(socketSslContext);
        }
#endif
    } else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
        state = QHttpNetworkConnectionChannel::IdleState;
        protocolHandler.reset(new QHttp2ProtocolHandler(this));
        if (h2RequestsToSend.count() > 0) {
            // In case our peer has sent us its settings (window size, max concurrent streams etc.)
            // let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection).
            QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
        }
    } else {
        state = QHttpNetworkConnectionChannel::IdleState;
        const bool tryProtocolUpgrade = connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2;
        if (tryProtocolUpgrade) {
            // For HTTP/1.1 it's already created and never reset.
            protocolHandler.reset(new QHttpProtocolHandler(this));
        }
        switchedToHttp2 = false;

        if (!reply)
            connection->d_func()->dequeueRequest(socket);

        if (reply) {
            if (tryProtocolUpgrade) {
                // Let's augment our request with some magic headers and try to
                // switch to HTTP/2.
                Http2::appendProtocolUpgradeHeaders(connection->http2Parameters(), &request);
            }
            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:
        // This error for SSL comes twice in a row, first from SSL layer ("The TLS/SSL connection has been closed") then from TCP layer.
        // Depending on timing it can also come three times in a row (first time when we try to write into a closing QSslSocket).
        // The reconnectAttempts handling catches the cases where we can re-send the request.
        if (!reply && state == QHttpNetworkConnectionChannel::IdleState) {
            // Not actually an error, it is normal for Keep-Alive connections to close after some time if no request
            // is sent on them. No need to error the other replies below. Just bail out here.
            // The _q_disconnected will handle the possibly pipelined replies. HTTP/2 is special for now,
            // we do not resend, but must report errors if any request is in progress (note, while
            // not in its sendRequest(), protocol handler switches the channel to IdleState, thus
            // this check is under this condition in 'if'):
            if (protocolHandler.data()) {
                if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct
                    || (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
                        && switchedToHttp2)) {
                    auto h2Handler = static_cast<QHttp2ProtocolHandler *>(protocolHandler.data());
                    h2Handler->handleConnectionClosure();
                }
            }
            return;
        } else if (state != QHttpNetworkConnectionChannel::IdleState && state != QHttpNetworkConnectionChannel::ReadingState) {
            // Try to reconnect/resend before sending an error.
            // While "Reading" the _q_disconnected() will handle this.
            // If we're using ssl then the protocolHandler is not initialized until
            // "encrypted" has been emitted, since retrying requires the protocolHandler (asserted)
            // we will not try if encryption is not done.
            if (!pendingEncrypt && reconnectAttempts-- > 0) {
                resendCurrentRequest();
                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
                // We need to invoke this asynchronously to make sure the state() of the socket is on QAbstractSocket::UnconnectedState
                QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
                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
                // We need to invoke this asynchronously to make sure the state() of the socket is on QAbstractSocket::UnconnectedState
                QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
                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 receive more data on the socket.
                reply->setReadBufferSize(0);
                reply->setDownstreamLimited(false);
                _q_receiveReply();
                if (!reply) {
                    // No more reply assigned after the previous call? Then it had been finished successfully.
                    requeueCurrentlyPipelinedRequests();
                    state = QHttpNetworkConnectionChannel::IdleState;
                    QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
                    return;
                }
            }

            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)) {
            resendCurrentRequest();
            return;
        }
        errorCode = QNetworkReply::TimeoutError;
        break;
    case QAbstractSocket::ProxyAuthenticationRequiredError:
        errorCode = QNetworkReply::ProxyAuthenticationRequiredError;
        break;
    case QAbstractSocket::SslHandshakeFailedError:
        errorCode = QNetworkReply::SslHandshakeFailedError;
        break;
    case QAbstractSocket::ProxyConnectionClosedError:
        // try to reconnect/resend before sending an error.
        if (reconnectAttempts-- > 0) {
            resendCurrentRequest();
            return;
        }
        errorCode = QNetworkReply::ProxyConnectionClosedError;
        break;
    case QAbstractSocket::ProxyConnectionTimeoutError:
        // try to reconnect/resend before sending an error.
        if (reconnectAttempts-- > 0) {
            resendCurrentRequest();
            return;
        }
        errorCode = QNetworkReply::ProxyTimeoutError;
        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());

    // In the HostLookupPending state the channel should not emit the error.
    // This will instead be handled by the connection.
    if (!connection->d_func()->shouldEmitChannelError(socket))
        return;

    // emit error for all waiting replies
    do {
        // First requeue the already pipelined requests for the current failed reply,
        // then dequeue pending requests so we can also mark them as finished with error
        if (reply)
            requeueCurrentlyPipelinedRequests();
        else
            connection->d_func()->dequeueRequest(socket);

        if (reply) {
            reply->d_func()->errorString = errorString;
            reply->d_func()->httpErrorCode = errorCode;
            emit reply->finishedWithError(errorCode, errorString);
            reply = nullptr;
            if (protocolHandler)
                protocolHandler->setReply(nullptr);
        }
    } while (!connection->d_func()->highPriorityQueue.isEmpty()
             || !connection->d_func()->lowPriorityQueue.isEmpty());

    if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
        || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
        QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
        for (int a = 0; a < h2Pairs.count(); ++a) {
            // emit error for all replies
            QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
            currentReply->d_func()->errorString = errorString;
            currentReply->d_func()->httpErrorCode = errorCode;
            Q_ASSERT(currentReply);
            emit currentReply->finishedWithError(errorCode, errorString);
        }
        h2RequestsToSend.clear();
    }

    // send the next request
    QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);

    if (that) {
        //signal emission triggered event loop
        if (!socket)
            state = QHttpNetworkConnectionChannel::IdleState;
        else if (socket->state() == QAbstractSocket::UnconnectedState)
            state = QHttpNetworkConnectionChannel::IdleState;
        else
            state = QHttpNetworkConnectionChannel::ClosingState;

        // pendingEncrypt must only be true in between connected and encrypted states
        pendingEncrypt = false;
    }
}

#ifndef QT_NO_NETWORKPROXY
void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth)
{
    if ((connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
         && (switchedToHttp2 || h2RequestsToSend.count() > 0))
        || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
        if (h2RequestsToSend.count() > 0)
            connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
    } else { // HTTP
        // 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)
        sendRequest();
}

void QHttpNetworkConnectionChannel::emitFinishedWithError(QNetworkReply::NetworkError error,
                                                          const char *message)
{
    if (reply)
        emit reply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
    QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
    for (int a = 0; a < h2Pairs.count(); ++a) {
        QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
        Q_ASSERT(currentReply);
        emit currentReply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
    }
}

#ifndef QT_NO_SSL
void QHttpNetworkConnectionChannel::_q_encrypted()
{
    QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket);
    Q_ASSERT(sslSocket);

    if (!protocolHandler && connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
        // ConnectionTypeHTTP2Direct does not rely on ALPN/NPN to negotiate HTTP/2,
        // after establishing a secure connection we immediately start sending
        // HTTP/2 frames.
        switch (sslSocket->sslConfiguration().nextProtocolNegotiationStatus()) {
        case QSslConfiguration::NextProtocolNegotiationNegotiated: {
            QByteArray nextProtocol = sslSocket->sslConfiguration().nextNegotiatedProtocol();
            if (nextProtocol == QSslConfiguration::NextProtocolHttp1_1) {
                // fall through to create a QHttpProtocolHandler
            } else if (nextProtocol == QSslConfiguration::ALPNProtocolHTTP2) {
                switchedToHttp2 = true;
                protocolHandler.reset(new QHttp2ProtocolHandler(this));
                connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP2);
                break;
            } else {
                emitFinishedWithError(QNetworkReply::SslHandshakeFailedError,
                                      "detected unknown Next Protocol Negotiation protocol");
                break;
            }
        }
            Q_FALLTHROUGH();
        case QSslConfiguration::NextProtocolNegotiationUnsupported: // No agreement, try HTTP/1(.1)
        case QSslConfiguration::NextProtocolNegotiationNone: {
            protocolHandler.reset(new QHttpProtocolHandler(this));

            QSslConfiguration newConfiguration = sslSocket->sslConfiguration();
            QList<QByteArray> protocols = newConfiguration.allowedNextProtocols();
            const int nProtocols = protocols.size();
            // Clear the protocol that we failed to negotiate, so we do not try
            // it again on other channels that our connection can create/open.
            if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2)
                protocols.removeAll(QSslConfiguration::ALPNProtocolHTTP2);

            if (nProtocols > protocols.size()) {
                newConfiguration.setAllowedNextProtocols(protocols);
                const int channelCount = connection->d_func()->channelCount;
                for (int i = 0; i < channelCount; ++i)
                    connection->d_func()->channels[i].setSslConfiguration(newConfiguration);
            }

            connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP);
            // We use only one channel for HTTP/2, but normally six for
            // HTTP/1.1 - let's restore this number to the reserved number of
            // channels:
            if (connection->d_func()->activeChannelCount < connection->d_func()->channelCount) {
                connection->d_func()->activeChannelCount = connection->d_func()->channelCount;
                // re-queue requests from HTTP/2 queue to HTTP queue, if any
                requeueHttp2Requests();
            }
            break;
        }
        default:
            emitFinishedWithError(QNetworkReply::SslHandshakeFailedError,
                                  "detected unknown Next Protocol Negotiation protocol");
        }
    } else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
               || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
        // We have to reset QHttp2ProtocolHandler's state machine, it's a new
        // connection and the handler's state is unique per connection.
        protocolHandler.reset(new QHttp2ProtocolHandler(this));
    }

    if (!socket)
        return; // ### error
    state = QHttpNetworkConnectionChannel::IdleState;
    pendingEncrypt = false;

    if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2 ||
        connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
        if (h2RequestsToSend.count() > 0) {
            // Similar to HTTP/1.1 counterpart below:
            const auto &h2Pairs = h2RequestsToSend.values(); // (request, reply)
            const auto &pair = h2Pairs.first();
            emit pair.second->encrypted();
            // In case our peer has sent us its settings (window size, max concurrent streams etc.)
            // let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection).
            QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
        }
    } else { // HTTP
        if (!reply)
            connection->d_func()->dequeueRequest(socket);
        if (reply) {
            reply->setHttp2WasUsed(false);
            Q_ASSERT(reply->d_func()->connectionChannel == this);
            emit reply->encrypted();
        }
        if (reply)
            sendRequestDelayed();
    }
}

void QHttpNetworkConnectionChannel::requeueHttp2Requests()
{
    QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
    for (int a = 0; a < h2Pairs.count(); ++a)
        connection->d_func()->requeueRequest(h2Pairs.at(a));
    h2RequestsToSend.clear();
}

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 (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP) {
        if (reply)
            emit reply->sslErrors(errors);
    }
#ifndef QT_NO_SSL
    else { // HTTP/2
        QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
        for (int a = 0; a < h2Pairs.count(); ++a) {
            // emit SSL errors for all replies
            QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
            Q_ASSERT(currentReply);
            emit currentReply->sslErrors(errors);
        }
    }
#endif // QT_NO_SSL
    connection->d_func()->resumeConnection();
}

void QHttpNetworkConnectionChannel::_q_preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator)
{
    connection->d_func()->pauseConnection();

    if (pendingEncrypt && !reply)
        connection->d_func()->dequeueRequest(socket);

    if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP) {
        if (reply)
            emit reply->preSharedKeyAuthenticationRequired(authenticator);
    } else {
        QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
        for (int a = 0; a < h2Pairs.count(); ++a) {
            // emit SSL errors for all replies
            QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
            Q_ASSERT(currentReply);
            emit currentReply->preSharedKeyAuthenticationRequired(authenticator);
        }
    }

    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
}

#endif

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.cpp"
