// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "minihttpserver.h" #include #include #include #include #include MiniHttpServer::MiniHttpServer(QObject *parent) : QThread(parent) { readyToGo = new QSemaphore; start(); readyToGo->acquire(); delete readyToGo; } MiniHttpServer::~MiniHttpServer() { quitObject->deleteLater(); wait(); } void MiniHttpServer::run() { server = new QTcpServer; server->listen(QHostAddress::LocalHost); portnum = server->serverPort(); connect(server, SIGNAL(newConnection()), this, SLOT(handleConnection()), Qt::DirectConnection); quitObject = new QObject; connect(quitObject, SIGNAL(destroyed()), this, SLOT(quit()), Qt::DirectConnection); readyToGo->release(); exec(); // cleanup delete server; } void MiniHttpServer::handleConnection() { while (server->hasPendingConnections()) { QTcpSocket *socket = server->nextPendingConnection(); new MiniHttpServerConnection(socket); // handles its own lifetime } } MiniHttpServerConnection::MiniHttpServerConnection(QTcpSocket *socket) : QObject(socket), socket(socket), source(0) { connect(socket, SIGNAL(readyRead()), SLOT(handleReadyRead())); connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(handleBytesWritten())); connect(socket, SIGNAL(disconnected()), SLOT(handleDisconnected())); timeout.setInterval(30000); timeout.setSingleShot(true); connect(&timeout, SIGNAL(timeout()), SLOT(handleTimeout())); timeout.start(); } void MiniHttpServerConnection::sendError500() { static const char body[] = "HTTP/1.1 500 Server Error\r\n" "Connection: close\r\n" "Content-Length: 0\r\n" "\r\n"; socket->write(body, strlen(body)); socket->disconnectFromHost(); } void MiniHttpServerConnection::sendError404() { static const char body1[] = "HTTP/1.1 404 File not found\r\n" "Content-Length: 0\r\n"; socket->write(body1, strlen(body1)); if (connectionClose) { socket->write("Connection: close\r\n\r\n"); socket->disconnectFromHost(); } else { socket->write("\r\n"); handlePendingRequest(); } } void MiniHttpServerConnection::handlePendingRequest() { int endOfRequest = buffer.indexOf("\r\n\r\n"); if (endOfRequest == -1) return; // nothing to do QByteArray request = buffer.left(endOfRequest); buffer = buffer.mid(endOfRequest + 4); //qDebug("request: %s", request.constData()); if (!request.startsWith("GET ")) { sendError500(); return; } int eol = request.indexOf("\r\n"); static const char http11[] = " HTTP/1.1"; if (memcmp(request.data() + eol - strlen(http11), http11, strlen(http11)) != 0) { sendError500(); return; } QUrl uri = QUrl::fromEncoded(request.mid(4, eol - int(strlen(http11)) - 4)); source.setFileName(QLatin1Char(':') + uri.path()); // connection-close? request = request.toLower(); connectionClose = request.contains("\r\nconnection: close\r\n"); if (!source.open(QIODevice::ReadOnly)) { sendError404(); return; } // success timeout.stop(); static const char body[] = "HTTP/1.1 200 Ok\r\n" "Content-Type: text/plain\r\n" "Content-Length: "; socket->write(body, strlen(body)); socket->write(QByteArray::number(source.size())); if (connectionClose) socket->write("\r\nConnection: close"); socket->write("\r\n\r\n"); handleBytesWritten(); } void MiniHttpServerConnection::handleReadyRead() { buffer += socket->readAll(); if (!source.isOpen()) handlePendingRequest(); } void MiniHttpServerConnection::handleDisconnected() { socket->deleteLater(); // will delete us too } void MiniHttpServerConnection::handleBytesWritten() { qint64 maxBytes = qMin(128*1024, source.bytesAvailable()); maxBytes = qMin(maxBytes, 128*1024 - socket->bytesToWrite()); if (maxBytes < 0) return; socket->write(source.read(maxBytes)); if (source.atEnd()) { // file ended source.close(); if (connectionClose) { socket->disconnectFromHost(); } else { timeout.start(); handlePendingRequest(); } } } void MiniHttpServerConnection::handleTimeout() { socket->disconnectFromHost(); }