mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-22 03:45:51 +08:00
263 lines
7.4 KiB
C++
263 lines
7.4 KiB
C++
/*
|
|
* iDescriptor: A free and open-source idevice management tool.
|
|
*
|
|
* Copyright (C) 2025 Uncore <https://github.com/uncor3>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "httpserver.h"
|
|
#include "iDescriptor.h"
|
|
#include "settingsmanager.h"
|
|
#include <QDateTime>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QMimeDatabase>
|
|
#include <QNetworkInterface>
|
|
#include <QRandomGenerator>
|
|
#include <QUrl>
|
|
|
|
HttpServer::HttpServer(QObject *parent)
|
|
: QObject(parent), server(new QTcpServer(this)), port(8080)
|
|
{
|
|
connect(server, &QTcpServer::newConnection, this,
|
|
&HttpServer::onNewConnection);
|
|
}
|
|
|
|
HttpServer::~HttpServer() { stop(); }
|
|
|
|
void HttpServer::start(const QStringList &files)
|
|
{
|
|
fileList = files;
|
|
|
|
// Generate unique JSON filename
|
|
QString timestamp =
|
|
QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss");
|
|
jsonFileName = QString("%1-idescriptor-import.json").arg(timestamp);
|
|
|
|
// Try to bind to port from settings, if fails try other ports
|
|
int startPort = SettingsManager::sharedInstance()->wirelessFileServerPort();
|
|
qDebug() << "Starting HTTP server on port" << startPort;
|
|
for (int tryPort = startPort; tryPort <= startPort + 10; ++tryPort) {
|
|
if (server->listen(QHostAddress::Any, tryPort)) {
|
|
port = tryPort;
|
|
emit serverStarted();
|
|
return;
|
|
}
|
|
}
|
|
|
|
emit serverError(QString("Could not bind to any port between %1-%2")
|
|
.arg(startPort)
|
|
.arg(startPort + 10));
|
|
}
|
|
|
|
void HttpServer::stop()
|
|
{
|
|
if (server->isListening()) {
|
|
server->close();
|
|
}
|
|
}
|
|
|
|
int HttpServer::getPort() const { return port; }
|
|
|
|
void HttpServer::onNewConnection()
|
|
{
|
|
QTcpSocket *socket = server->nextPendingConnection();
|
|
connect(socket, &QTcpSocket::readyRead, this, &HttpServer::onReadyRead);
|
|
connect(socket, &QTcpSocket::disconnected, this,
|
|
&HttpServer::onDisconnected);
|
|
}
|
|
|
|
void HttpServer::onReadyRead()
|
|
{
|
|
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
|
|
if (!socket)
|
|
return;
|
|
|
|
QByteArray data = socket->readAll();
|
|
QString request = QString::fromUtf8(data);
|
|
|
|
// Parse HTTP request
|
|
QStringList lines = request.split("\r\n");
|
|
if (lines.isEmpty())
|
|
return;
|
|
|
|
QString requestLine = lines.first();
|
|
QStringList parts = requestLine.split(" ");
|
|
if (parts.size() < 2)
|
|
return;
|
|
|
|
QString method = parts[0];
|
|
QString path = parts[1];
|
|
|
|
if (method == "GET") {
|
|
handleRequest(socket, path);
|
|
} else {
|
|
sendResponse(socket, 405, "text/plain", "Method Not Allowed");
|
|
}
|
|
}
|
|
|
|
void HttpServer::onDisconnected()
|
|
{
|
|
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
|
|
if (socket) {
|
|
socket->deleteLater();
|
|
}
|
|
}
|
|
|
|
void HttpServer::handleRequest(QTcpSocket *socket, const QString &path)
|
|
{
|
|
// Serve JSON manifest
|
|
if (path == QString("/%1").arg(jsonFileName)) {
|
|
sendJsonManifest(socket);
|
|
return;
|
|
}
|
|
|
|
// Serve files from /serve/ directory
|
|
if (path.startsWith("/serve/")) {
|
|
QString encodedFileName = path.mid(7); // Remove "/serve/"
|
|
QString fileName = QUrl::fromPercentEncoding(encodedFileName.toUtf8());
|
|
|
|
// Find the file in our list
|
|
QString targetFile;
|
|
for (const QString &file : fileList) {
|
|
QFileInfo info(file);
|
|
if (info.fileName() == fileName) {
|
|
targetFile = file;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!targetFile.isEmpty()) {
|
|
sendFile(socket, targetFile);
|
|
return;
|
|
}
|
|
}
|
|
|
|
sendResponse(socket, 404, "text/html",
|
|
"<html><body><h1>404 Not Found</h1><p>The requested file was "
|
|
"not found.</p></body></html>");
|
|
}
|
|
|
|
void HttpServer::sendResponse(QTcpSocket *socket, int statusCode,
|
|
const QString &contentType,
|
|
const QByteArray &data)
|
|
{
|
|
QString statusText;
|
|
switch (statusCode) {
|
|
case 200:
|
|
statusText = "OK";
|
|
break;
|
|
case 404:
|
|
statusText = "Not Found";
|
|
break;
|
|
case 405:
|
|
statusText = "Method Not Allowed";
|
|
break;
|
|
case 500:
|
|
statusText = "Internal Server Error";
|
|
break;
|
|
default:
|
|
statusText = "Unknown";
|
|
break;
|
|
}
|
|
|
|
QString response =
|
|
QString("HTTP/1.1 %1 %2\r\n").arg(statusCode).arg(statusText);
|
|
response += QString("Content-Type: %1\r\n").arg(contentType);
|
|
response += QString("Content-Length: %1\r\n").arg(data.size());
|
|
response += "Access-Control-Allow-Origin: *\r\n";
|
|
response += "Connection: close\r\n";
|
|
response += "\r\n";
|
|
|
|
socket->write(response.toUtf8());
|
|
socket->write(data);
|
|
socket->disconnectFromHost();
|
|
}
|
|
|
|
void HttpServer::sendFile(QTcpSocket *socket, const QString &filePath)
|
|
{
|
|
QFile file(filePath);
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
sendResponse(socket, 404, "text/plain", "File not found");
|
|
return;
|
|
}
|
|
|
|
QByteArray data = file.readAll();
|
|
QString mimeType = getMimeType(filePath);
|
|
|
|
// Emit progress signal
|
|
QFileInfo info(filePath);
|
|
emit downloadProgress(info.fileName(), data.size(), data.size());
|
|
|
|
sendResponse(socket, 200, mimeType, data);
|
|
}
|
|
|
|
void HttpServer::sendJsonManifest(QTcpSocket *socket)
|
|
{
|
|
QString jsonContent = generateJsonManifest();
|
|
sendResponse(socket, 200, "application/json", jsonContent.toUtf8());
|
|
}
|
|
|
|
QString HttpServer::generateJsonManifest() const
|
|
{
|
|
QString serverIP = getLocalIP();
|
|
|
|
QJsonObject manifest;
|
|
QJsonArray items;
|
|
|
|
for (const QString &file : fileList) {
|
|
QFileInfo info(file);
|
|
QJsonObject item;
|
|
item["path"] = QString("http://%1:%2/serve/%3")
|
|
.arg(serverIP)
|
|
.arg(port)
|
|
.arg(QString::fromUtf8(
|
|
QUrl::toPercentEncoding(info.fileName())));
|
|
items.append(item);
|
|
}
|
|
|
|
manifest["items"] = items;
|
|
|
|
QJsonDocument doc(manifest);
|
|
return doc.toJson();
|
|
}
|
|
|
|
QString HttpServer::getLocalIP()
|
|
{
|
|
foreach (const QNetworkInterface &netIf,
|
|
QNetworkInterface::allInterfaces()) {
|
|
if (netIf.flags().testFlag(QNetworkInterface::IsUp) &&
|
|
!netIf.flags().testFlag(QNetworkInterface::IsLoopBack)) {
|
|
foreach (const QNetworkAddressEntry &entry,
|
|
netIf.addressEntries()) {
|
|
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
|
|
return entry.ip().toString();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "127.0.0.1";
|
|
}
|
|
|
|
QString HttpServer::getMimeType(const QString &filePath) const
|
|
{
|
|
QMimeDatabase db;
|
|
QMimeType type = db.mimeTypeForFile(filePath);
|
|
return type.name();
|
|
}
|