Revision control
++ // Copyright (C) 2024 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qhttpserverhttp1protocolhandler_p.h"
++
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qmetaobject.h>
++ #include <QtCore/qthread.h>
++ #include <QtCore/qpointer.h>
++ #include <QtHttpServer/qabstracthttpserver.h>
++ #include <QtHttpServer/qhttpserverrequest.h>
++ #include <QtHttpServer/qhttpserverresponder.h>
++ #include <QtHttpServer/qhttpserverresponse.h>
++ #if QT_CONFIG(localserver)
++ #include <QtNetwork/qlocalsocket.h>
++ #endif
++ #include <QtNetwork/qtcpsocket.h>
++
++ #include <private/qabstracthttpserver_p.h>
++ #include <private/qhttpserverliterals_p.h>
++ #include <private/qhttpserverrequest_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ Q_STATIC_LOGGING_CATEGORY(lcHttpServerHttp1Handler, "qt.httpserver.http1handler")
++
++ static const std::map<QHttpServerResponder::StatusCode, QByteArray> statusString{
++ #define XX(name, string) { QHttpServerResponder::StatusCode::name, QByteArrayLiteral(string) }
++ XX(Continue, "Continue"),
++ XX(SwitchingProtocols, "Switching Protocols"),
++ XX(Processing, "Processing"),
++ XX(Ok, "OK"),
++ XX(Created, "Created"),
++ XX(Accepted, "Accepted"),
++ XX(NonAuthoritativeInformation, "Non-Authoritative Information"),
++ XX(NoContent, "No Content"),
++ XX(ResetContent, "Reset Content"),
++ XX(PartialContent, "Partial Content"),
++ XX(MultiStatus, "Multi-Status"),
++ XX(AlreadyReported, "Already Reported"),
++ XX(IMUsed, "I'm Used"),
++ XX(MultipleChoices, "Multiple Choices"),
++ XX(MovedPermanently, "Moved Permanently"),
++ XX(Found, "Found"),
++ XX(SeeOther, "See Other"),
++ XX(NotModified, "Not Modified"),
++ XX(UseProxy, "Use Proxy"),
++ XX(TemporaryRedirect, "Temporary Redirect"),
++ XX(PermanentRedirect, "Permanent Redirect"),
++ XX(BadRequest, "Bad Request"),
++ XX(Unauthorized, "Unauthorized"),
++ XX(PaymentRequired, "Payment Required"),
++ XX(Forbidden, "Forbidden"),
++ XX(NotFound, "Not Found"),
++ XX(MethodNotAllowed, "Method Not Allowed"),
++ XX(NotAcceptable, "Not Acceptable"),
++ XX(ProxyAuthenticationRequired, "Proxy Authentication Required"),
++ XX(RequestTimeout, "Request Timeout"),
++ XX(Conflict, "Conflict"),
++ XX(Gone, "Gone"),
++ XX(LengthRequired, "Length Required"),
++ XX(PreconditionFailed, "Precondition Failed"),
++ XX(PayloadTooLarge, "Request Entity Too Large"),
++ XX(UriTooLong, "Request-URI Too Long"),
++ XX(UnsupportedMediaType, "Unsupported Media Type"),
++ XX(RequestRangeNotSatisfiable, "Requested Range Not Satisfiable"),
++ XX(ExpectationFailed, "Expectation Failed"),
++ XX(ImATeapot, "I'm a teapot"),
++ XX(MisdirectedRequest, "Misdirected Request"),
++ XX(UnprocessableEntity, "Unprocessable Entity"),
++ XX(Locked, "Locked"),
++ XX(FailedDependency, "Failed Dependency"),
++ XX(UpgradeRequired, "Upgrade Required"),
++ XX(PreconditionRequired, "Precondition Required"),
++ XX(TooManyRequests, "Too Many Requests"),
++ XX(RequestHeaderFieldsTooLarge, "Request Header Fields Too Large"),
++ XX(UnavailableForLegalReasons, "Unavailable For Legal Reasons"),
++ XX(InternalServerError, "Internal Server Error"),
++ XX(NotImplemented, "Not Implemented"),
++ XX(BadGateway, "Bad Gateway"),
++ XX(ServiceUnavailable, "Service Unavailable"),
++ XX(GatewayTimeout, "Gateway Timeout"),
++ XX(HttpVersionNotSupported, "HTTP Version Not Supported"),
++ XX(VariantAlsoNegotiates, "Variant Also Negotiates"),
++ XX(InsufficientStorage, "Insufficient Storage"),
++ XX(LoopDetected, "Loop Detected"),
++ XX(NotExtended, "Not Extended"),
++ XX(NetworkAuthenticationRequired, "Network Authentication Required"),
++ XX(NetworkConnectTimeoutError, "Network Connect Timeout Error"),
++ #undef XX
++ };
++
++ template <qint64 BUFFERSIZE = 128 * 1024>
++ struct QHttpServerHttp1IOChunkedTransfer
++ {
++ // TODO This is not the fastest implementation, as it does read & write
++ // in a sequential fashion, but these operation could potentially overlap.
++ // TODO Can we implement it without the buffer? Direct write to the target buffer
++ // would be great.
++
++ static constexpr qint64 bufferSize = BUFFERSIZE;
++ static constexpr qint64 targetWriteBufferSaturation = bufferSize / 2;
++ char buffer[BUFFERSIZE];
++ qint64 beginIndex = -1;
++ qint64 endIndex = -1;
++ QPointer<QIODevice> source;
++ const QPointer<QIODevice> sink;
++ QMetaObject::Connection bytesWrittenConnection;
++ QMetaObject::Connection readyReadConnection;
++ QMetaObject::Connection readChannelFinished;
++ QPointer<QHttpServerHttp1ProtocolHandler> handler;
++ bool inRead = false;
++ bool gotReadChannelFinished = false;
++ bool writingIsComplete = false;
++ bool useHttp1_1 = false;
++
++ QHttpServerHttp1IOChunkedTransfer(QIODevice *input, QIODevice *output, bool http1_1,
++ QHttpServerHttp1ProtocolHandler *callback) :
++ source(input),
++ sink(output),
++ bytesWrittenConnection(connectToBytesWritten(this, output)),
++ readyReadConnection(QObject::connect(source.data(), &QIODevice::readyRead, source.data(),
++ [this]() { readFromInput(); })),
++ readChannelFinished(QObject::connect(source.data(), &QIODevice::readChannelFinished,
++ source.data(),
++ [this]() { readChannelFinishedHandler(); })),
++ handler(callback),
++ useHttp1_1(http1_1)
++ {
++ QObject::connect(sink.data(), &QObject::destroyed, source.data(), &QObject::deleteLater);
++ QObject::connect(source.data(), &QObject::destroyed, source.data(), [this]() {
++ delete this;
++ });
++ readFromInput();
++ }
++
++ ~QHttpServerHttp1IOChunkedTransfer()
++ {
++ QObject::disconnect(bytesWrittenConnection);
++ QObject::disconnect(readyReadConnection);
++ QObject::disconnect(readChannelFinished);
++ }
++
++ static QMetaObject::Connection connectToBytesWritten(QHttpServerHttp1IOChunkedTransfer *that,
++ QIODevice *device)
++ {
++ auto send = [that]() { that->writeToOutput(); };
++ #if QT_CONFIG(ssl)
++ if (auto *sslSocket = qobject_cast<QSslSocket *>(device)) {
++ return QObject::connect(sslSocket, &QSslSocket::encryptedBytesWritten, sslSocket,
++ std::move(send));
++ }
++ #endif
++ return QObject::connect(device, &QIODevice::bytesWritten, device, std::move(send));
++ }
++
++ inline bool isBufferEmpty()
++ {
++ Q_ASSERT(beginIndex <= endIndex);
++ return beginIndex == endIndex;
++ }
++
++ void readChannelFinishedHandler()
++ {
++ gotReadChannelFinished = true;
++ readFromInput();
++ }
++
++ void readFromInput()
++ {
++ if (inRead)
++ return;
++ if (source.isNull())
++ return;
++
++ if (!isBufferEmpty()) // We haven't consumed all the data yet.
++ return;
++ QScopedValueRollback inReadGuard(inRead, true);
++
++ while (isBufferEmpty()) {
++ beginIndex = 0;
++ endIndex = source->read(buffer, bufferSize);
++ if (endIndex < 0) {
++ endIndex = 0; // Mark the buffer as empty
++ if (!source->isSequential()) {
++ qCWarning(lcHttpServerHttp1Handler, "Error reading chunk: %ls",
++ qUtf16Printable(source->errorString()));
++ return;
++ }
++ }
++ if (endIndex == 0) { // Nothing was read
++ if (!writingIsComplete
++ && ((!source->isSequential() && source->atEnd()) || gotReadChannelFinished))
++ completeWriting();
++ return;
++ }
++ memset(buffer + endIndex, 0, sizeof(buffer) - std::size_t(endIndex));
++ writeToOutput();
++ }
++ }
++
++ void writeToOutput()
++ {
++ if (sink.isNull() || source.isNull())
++ return;
++ if (isBufferEmpty())
++ return;
++
++ // If downstream has enough data to write already,
++ // don't bother writing more now. That would only lead to
++ // higher, unnecessary memory usage.
++ if (sink->bytesToWrite() >= targetWriteBufferSaturation)
++ return;
++ #if QT_CONFIG(ssl)
++ if (auto *sslSocket = qobject_cast<QSslSocket *>(sink.data())) {
++ const qint64 budget = targetWriteBufferSaturation - sink->bytesToWrite();
++ if (sslSocket->encryptedBytesToWrite() >= budget)
++ return;
++ }
++ #endif
++
++ if (useHttp1_1 && source->isSequential()) {
++ sink->write(QByteArray::number(endIndex - beginIndex, 16));
++ sink->write("\r\n");
++ }
++ const auto writtenBytes = sink->write(buffer + beginIndex, endIndex - beginIndex);
++ if (writtenBytes < 0) {
++ qCWarning(lcHttpServerHttp1Handler, "Error writing chunk: %ls",
++ qUtf16Printable(sink->errorString()));
++ return;
++ }
++ if (useHttp1_1 && source->isSequential())
++ sink->write("\r\n");
++ beginIndex += writtenBytes;
++ if (isBufferEmpty() && !inRead)
++ readFromInput();
++ }
++
++ void completeWriting()
++ {
++ if (sink.isNull())
++ return;
++ Q_ASSERT(!source.isNull());
++ Q_ASSERT(isBufferEmpty());
++
++ if (source->isSequential()) {
++ if (useHttp1_1)
++ sink->write("0\r\n\r\n");
++ }
++ source->deleteLater();
++ writingIsComplete = true;
++ if (!handler.isNull())
++ handler->completeWriting();
++ }
++ };
++
++ QHttpServerHttp1ProtocolHandler::QHttpServerHttp1ProtocolHandler(QAbstractHttpServer *server,
++ QIODevice *socket,
++ QHttpServerRequestFilter *filter,
++ QHttpServerConfiguration *config)
++ : QHttpServerStream(socket, filter, server),
++ server(server),
++ socket(socket),
++ tcpSocket(qobject_cast<QTcpSocket *>(socket)),
++ #if QT_CONFIG(localserver)
++ localSocket(qobject_cast<QLocalSocket*>(socket)),
++ #endif
++ m_filter(filter),
++ m_configuration(config)
++ {
++ socket->setParent(this);
++
++ if (tcpSocket) {
++ qCDebug(lcHttpServerHttp1Handler) << "Connection from:" << tcpSocket->peerAddress();
++ connect(socket, &QTcpSocket::readyRead,
++ this, &QHttpServerHttp1ProtocolHandler::handleReadyRead);
++ connect(tcpSocket, &QTcpSocket::disconnected,
++ this, &QHttpServerHttp1ProtocolHandler::socketDisconnected);
++ #if QT_CONFIG(localserver)
++ } else if (localSocket) {
++ qCDebug(lcHttpServerHttp1Handler) << "Connection from:" << localSocket->serverName();
++ connect(socket, &QLocalSocket::readyRead,
++ this, &QHttpServerHttp1ProtocolHandler::handleReadyRead);
++ connect(localSocket, &QLocalSocket::disconnected,
++ this, &QHttpServerHttp1ProtocolHandler::socketDisconnected);
++ #endif
++ }
++
++ lastActiveTimer.start();
++ }
++
++ void QHttpServerHttp1ProtocolHandler::responderDestroyed(quint32 streamId)
++ {
++ disconnectResponder(streamId);
++ Q_ASSERT(QThread::currentThread() == thread());
++ if (protocolChanged) {
++ deleteLater();
++ return;
++ }
++ Q_ASSERT(handlingRequest);
++ handlingRequest = false;
++ if (state == TransferState::Ready)
++ resumeListening();
++ }
++
++ void QHttpServerHttp1ProtocolHandler::resumeListening()
++ {
++ if (tcpSocket) {
++ if (tcpSocket->state() != QAbstractSocket::ConnectedState) {
++ deleteLater();
++ } else {
++ connect(tcpSocket, &QTcpSocket::readyRead,
++ this, &QHttpServerHttp1ProtocolHandler::handleReadyRead);
++ QMetaObject::invokeMethod(tcpSocket, &QTcpSocket::readyRead, Qt::QueuedConnection);
++ }
++ #if QT_CONFIG(localserver)
++ } else if (localSocket) {
++ if (localSocket->state() != QLocalSocket::ConnectedState) {
++ deleteLater();
++ } else {
++ connect(localSocket, &QLocalSocket::readyRead,
++ this, &QHttpServerHttp1ProtocolHandler::handleReadyRead);
++ QMetaObject::invokeMethod(localSocket, &QLocalSocket::readyRead, Qt::QueuedConnection);
++ }
++ #endif
++ }
++ }
++
++ void QHttpServerHttp1ProtocolHandler::startHandlingRequest()
++ {
++ handlingRequest = true;
++ }
++
++ void QHttpServerHttp1ProtocolHandler::socketDisconnected()
++ {
++ if (!handlingRequest)
++ deleteLater();
++ }
++
++ void QHttpServerHttp1ProtocolHandler::handleReadyRead()
++ {
++ if (handlingRequest || state != TransferState::Ready)
++ return;
++
++ lastActiveTimer.restart();
++
++ if (!socket->isTransactionStarted())
++ socket->startTransaction();
++
++ auto requestReceived = parser.parse(socket);
++ if (!requestReceived) {
++ if (tcpSocket)
++ tcpSocket->disconnectFromHost();
++ #if QT_CONFIG(localserver)
++ else if (localSocket)
++ localSocket->disconnectFromServer();
++ #endif
++ return;
++ }
++
++ #if QT_CONFIG(ssl)
++ auto request = QHttpServerRequest::create(parser, sslConfiguration);
++ #else
++ auto request = QHttpServerRequest::create(parser);
++ #endif
++
++ if (parser.state != QHttpServerParser::State::AllDone)
++ return; // Partial read
++
++ qCDebug(lcHttpServerHttp1Handler) << "Request:" << request;
++ useHttp1_1 = request.d->minorVersion == 1;
++ auto connectionHeader =
++ QByteArray(request.headers().value(QHttpHeaders::WellKnownHeader::Connection,
++ useHttp1_1 ? "keep-alive" : "close")).toLower();
++ keepAlive = connectionHeader.contains("keep-alive");
++
++ QHttpServerResponder responder(this);
++ connectResponder(responder.d_ptr);
++
++ #if defined(QT_WEBSOCKETS_LIB)
++ if (auto *tcpSocket = qobject_cast<QTcpSocket*>(socket)) {
++ if (parser.upgrade) { // Upgrade
++ const auto &upgradeValue = request.value(QByteArrayLiteral("upgrade"));
++ if (upgradeValue.compare(QByteArrayLiteral("websocket"), Qt::CaseInsensitive) == 0) {
++ const auto upgradeResponse = server->verifyWebSocketUpgrade(request);
++ static const auto signal =
++ QMetaMethod::fromSignal(&QAbstractHttpServer::newWebSocketConnection);
++ if (server->isSignalConnected(signal)
++ && upgradeResponse.type()
++ != QHttpServerWebSocketUpgradeResponse::ResponseType::PassToNext) {
++ if (upgradeResponse.type()
++ == QHttpServerWebSocketUpgradeResponse::ResponseType::Accept) {
++ // Socket will now be managed by websocketServer
++ protocolChanged = true;
++ socket->disconnect();
++ socket->rollbackTransaction();
++ socket->setParent(nullptr);
++ server->d_func()->websocketServer.handleConnection(tcpSocket);
++ Q_EMIT socket->readyRead();
++ } else {
++ qCDebug(lcHttpServerHttp1Handler, "WebSocket upgrade denied: %ls",
++ qUtf16Printable(QLatin1StringView(upgradeResponse.denyMessage())));
++ QByteArray buffer;
++ buffer.append("HTTP/1.1 ");
++ buffer.append(QByteArray::number(quint32(upgradeResponse.denyStatus())));
++ buffer.append(" ");
++ buffer.append(upgradeResponse.denyMessage());
++ buffer.append("\r\n\r\n");
++ tcpSocket->write(buffer);
++ }
++ } else {
++ if (!server->isSignalConnected(signal)) {
++ qCWarning(lcHttpServerHttp1Handler,
++ "WebSocket received but no slots connected to "
++ "QWebSocketServer::newConnection");
++ }
++ server->missingHandler(request, responder);
++ tcpSocket->disconnectFromHost();
++ }
++ return;
++ }
++ }
++ }
++ #endif // QT_WEBSOCKETS_LIB
++
++ socket->commitTransaction();
++
++ QHostAddress peerAddress = tcpSocket ? tcpSocket->peerAddress()
++ : QHostAddress::LocalHost;
++ if (!m_filter->isRequestAllowed(peerAddress)) {
++ responder.sendResponse(
++ QHttpServerResponse(QHttpServerResponder::StatusCode::Forbidden));
++ }
++ if (!m_filter->isRequestWithinRate(peerAddress)) {
++ responder.sendResponse(
++ QHttpServerResponse(QHttpServerResponder::StatusCode::TooManyRequests));
++ } else if (!server->handleRequest(request, responder)) {
++ server->missingHandler(request, responder);
++ }
++
++ if (handlingRequest || state != TransferState::Ready)
++ disconnect(socket, &QIODevice::readyRead, this, &QHttpServerHttp1ProtocolHandler::handleReadyRead);
++ else if (socket->bytesAvailable() > 0)
++ QMetaObject::invokeMethod(socket, &QIODevice::readyRead, Qt::QueuedConnection);
++ }
++
++ void QHttpServerHttp1ProtocolHandler::write(const QByteArray &body, const QHttpHeaders &headers,
++ QHttpServerResponder::StatusCode status, quint32 streamId)
++ {
++ Q_UNUSED(streamId);
++ Q_ASSERT(state == TransferState::Ready);
++ QHttpHeaders allHeaders(headers);
++ addConnectionAndKeepAliveHeaders(allHeaders);
++ writeStatusAndHeaders(status, allHeaders);
++ write(body);
++ state = TransferState::Ready;
++ if (!keepAlive)
++ closeConnection();
++ }
++
++ void QHttpServerHttp1ProtocolHandler::write(QHttpServerResponder::StatusCode status, quint32 streamId)
++ {
++ Q_UNUSED(streamId);
++ Q_ASSERT(state == TransferState::Ready);
++ QHttpHeaders headers;
++ headers.append(QHttpHeaders::WellKnownHeader::ContentType,
++ QHttpServerLiterals::contentTypeXEmpty());
++ headers.append(QHttpHeaders::WellKnownHeader::ContentLength, "0");
++ addConnectionAndKeepAliveHeaders(headers);
++ writeStatusAndHeaders(status, headers);
++ state = TransferState::Ready;
++ if (!keepAlive)
++ closeConnection();
++ }
++
++ void QHttpServerHttp1ProtocolHandler::write(QIODevice *data, const QHttpHeaders &headers,
++ QHttpServerResponder::StatusCode status, quint32 streamId)
++ {
++ Q_UNUSED(streamId);
++ Q_ASSERT(state == TransferState::Ready);
++ std::unique_ptr<QIODevice, QScopedPointerDeleteLater> input(data);
++
++ input->setParent(nullptr);
++ if (!input->isOpen()) {
++ if (!input->open(QIODevice::ReadOnly)) {
++ // TODO Add developer error handling
++ qCDebug(lcHttpServerHttp1Handler, "500: Could not open device %ls",
++ qUtf16Printable(input->errorString()));
++ write(QHttpServerResponder::StatusCode::InternalServerError, 0);
++ return;
++ }
++ } else if (!(input->openMode() & QIODevice::ReadOnly)) {
++ // TODO Add developer error handling
++ qCDebug(lcHttpServerHttp1Handler) << "500: Device is opened in a wrong mode"
++ << input->openMode();
++ write(QHttpServerResponder::StatusCode::InternalServerError, 0);
++ return;
++ }
++
++ QHttpHeaders allHeaders(headers);
++ if (input->isSequential()) {
++ if (useHttp1_1)
++ allHeaders.append(QHttpHeaders::WellKnownHeader::TransferEncoding, "chunked");
++ else
++ keepAlive = false;
++ } else { // Non-sequential QIODevice should know its data size
++ allHeaders.append(QHttpHeaders::WellKnownHeader::ContentLength,
++ QByteArray::number(input->size()));
++ }
++
++ addConnectionAndKeepAliveHeaders(allHeaders);
++ writeStatusAndHeaders(status, allHeaders);
++
++ state = TransferState::IODeviceTransferBegun;
++ // input takes ownership of the QHttpServerHttp1IOChunkedTransfer pointer inside his constructor
++ new QHttpServerHttp1IOChunkedTransfer<>(input.release(), socket, useHttp1_1, this);
++ }
++
++ void QHttpServerHttp1ProtocolHandler::writeBeginChunked(const QHttpHeaders &headers,
++ QHttpServerResponder::StatusCode status,
++ quint32 streamId)
++ {
++ Q_UNUSED(streamId);
++ Q_ASSERT(state == TransferState::Ready);
++ QHttpHeaders allHeaders(headers);
++ if (useHttp1_1)
++ allHeaders.append(QHttpHeaders::WellKnownHeader::TransferEncoding, "chunked");
++ else
++ keepAlive = false;
++
++ addConnectionAndKeepAliveHeaders(allHeaders);
++ writeStatusAndHeaders(status, allHeaders);
++ state = TransferState::ChunkedTransferBegun;
++ }
++
++ void QHttpServerHttp1ProtocolHandler::writeChunk(const QByteArray &data, quint32 streamId)
++ {
++ Q_UNUSED(streamId);
++ Q_ASSERT(state == TransferState::ChunkedTransferBegun);
++
++ if (data.length() == 0) {
++ qCWarning(lcHttpServerHttp1Handler, "Chunk must have length > 0");
++ return;
++ }
++
++ if (useHttp1_1) {
++ write(QByteArray::number(data.length(), 16));
++ write("\r\n");
++ }
++ write(data);
++ if (useHttp1_1)
++ write("\r\n");
++ }
++
++ void QHttpServerHttp1ProtocolHandler::writeEndChunked(const QByteArray &data,
++ const QHttpHeaders &trailers,
++ quint32 streamId)
++ {
++ Q_UNUSED(streamId);
++ Q_ASSERT(state == TransferState::ChunkedTransferBegun);
++ writeChunk(data, 0);
++ if (useHttp1_1) {
++ write("0\r\n");
++ for (qsizetype i = 0; i < trailers.size(); ++i) {
++ const auto name = trailers.nameAt(i);
++ const auto value = trailers.valueAt(i);
++ writeHeader({ name.data(), name.size() }, value.toByteArray());
++ }
++ write("\r\n");
++ } else {
++ if (!trailers.isEmpty())
++ qCWarning(lcHttpServerHttp1Handler, "Cannot send trailers for HTTP/1.0 connections.");
++ }
++ if (!keepAlive)
++ closeConnection();
++
++ state = TransferState::Ready;
++ }
++
++ void QHttpServerHttp1ProtocolHandler::writeStatusAndHeaders(QHttpServerResponder::StatusCode status,
++ const QHttpHeaders &headers)
++ {
++ Q_ASSERT(state == TransferState::Ready);
++ QByteArray payload;
++ payload.append(useHttp1_1 ? "HTTP/1.1 " : "HTTP/1.0 ");
++ payload.append(QByteArray::number(quint32(status)));
++ const auto it = statusString.find(status);
++ if (it != statusString.end()) {
++ payload.append(" ");
++ payload.append(statusString.at(status));
++ }
++ payload.append("\r\n");
++
++ for (qsizetype i = 0; i < headers.size(); ++i) {
++ const auto name = headers.nameAt(i);
++ payload.append(QByteArrayView(name.data(), name.size()) + ": "
++ + headers.valueAt(i).toByteArray() + "\r\n");
++ }
++ payload.append("\r\n");
++ write(payload);
++ state = TransferState::HeadersSent;
++ }
++
++ void QHttpServerHttp1ProtocolHandler::writeHeader(const QByteArray &key, const QByteArray &value)
++ {
++ write(key + ": " + value + "\r\n");
++ }
++
++ void QHttpServerHttp1ProtocolHandler::write(const QByteArray &ba)
++ {
++ Q_ASSERT(QThread::currentThread() == thread());
++ socket->write(ba);
++ }
++
++ void QHttpServerHttp1ProtocolHandler::write(const char *body, qint64 size)
++ {
++ Q_ASSERT(QThread::currentThread() == thread());
++ socket->write(body, size);
++ }
++
++ void QHttpServerHttp1ProtocolHandler::completeWriting()
++ {
++ Q_ASSERT(state == TransferState::IODeviceTransferBegun);
++ state = TransferState::Ready;
++ if (!keepAlive) {
++ closeConnection();
++ } else if (!handlingRequest) {
++ resumeListening();
++ }
++ }
++
++ void QHttpServerHttp1ProtocolHandler::addConnectionAndKeepAliveHeaders(QHttpHeaders &headers)
++ {
++ if (keepAlive) {
++ headers.append(QHttpHeaders::WellKnownHeader::Connection, "keep-alive");
++ headers.append(QHttpHeaders::WellKnownHeader::KeepAlive,
++ u"timeout=%1"_s.arg(m_configuration->keepAliveTimeout().count()));
++ } else {
++ headers.append(QHttpHeaders::WellKnownHeader::Connection, "close");
++ }
++ }
++
++ void QHttpServerHttp1ProtocolHandler::checkKeepAliveTimeout()
++ {
++ if (handlingRequest || state != TransferState::Ready)
++ return;
++
++ if (lastActiveTimer.durationElapsed() > server->configuration().keepAliveTimeout()) {
++ if (tcpSocket)
++ tcpSocket->abort();
++ #if QT_CONFIG(localserver)
++ else if (localSocket)
++ localSocket->abort();
++ #endif
++ }
++ }
++
++ void QHttpServerHttp1ProtocolHandler::closeConnection()
++ {
++ if (tcpSocket) {
++ QMetaObject::invokeMethod(tcpSocket, &QTcpSocket::disconnectFromHost, Qt::QueuedConnection);
++ #if QT_CONFIG(localserver)
++ } else if (localSocket) {
++ QMetaObject::invokeMethod(localSocket, &QLocalSocket::disconnectFromServer,
++ Qt::QueuedConnection);
++ #endif
++ }
++ }
++
++ QT_END_NAMESPACE