Files
ps4-ezremote-server/source/server/http_server.cpp
T
2026-05-30 04:05:31 -07:00

574 lines
21 KiB
C++

#include <string>
#include <json-c/json.h>
#include "http/httplib.h"
#include "server/http_server.h"
#include "clients/remote_client.h"
#include "clients/archiveorg.h"
#include "clients/baseclient.h"
#include "clients/ftpclient.h"
#include "clients/nfsclient.h"
#include "clients/smbclient.h"
#include "clients/sftpclient.h"
#include "clients/webdav.h"
#include "config.h"
#include "fs.h"
#include "util.h"
#define SUCCESS_MSG "{ \"result\": { \"success\": true, \"error\": null } }"
#define FAILURE_MSG "{ \"result\": { \"success\": false, \"error\": \"%s\" } }"
#define SUCCESS_MSG_LEN 48
#define PKG_INITIAL_REQUEST_SIZE 8388608ul
using namespace httplib;
Server *svr;
int http_server_port = 6701;
static pthread_t bg_download_thread;
static uint64_t g_dl_offset;
namespace HttpServer
{
static int FtpCallback(int64_t xfered, void *arg)
{
return 1;
}
static int DownloadFtpCallback(int64_t xfered, void *arg)
{
*g_bytes_transfered = g_dl_offset + xfered;
return 1;
}
std::string dump_headers(const Headers &headers)
{
std::string s;
char buf[BUFSIZ];
for (auto it = headers.begin(); it != headers.end(); ++it)
{
const auto &x = *it;
snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str());
s += buf;
}
return s;
}
std::string log(const Request &req, const Response &res)
{
std::string s;
char buf[BUFSIZ];
s += "================================\n";
snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(),
req.version.c_str(), req.path.c_str());
s += buf;
std::string query;
for (auto it = req.params.begin(); it != req.params.end(); ++it)
{
const auto &x = *it;
snprintf(buf, sizeof(buf), "%c%s=%s",
(it == req.params.begin()) ? '?' : '&', x.first.c_str(),
x.second.c_str());
query += buf;
}
snprintf(buf, sizeof(buf), "%s\n", query.c_str());
s += buf;
s += dump_headers(req.headers);
s += "--------------------------------\n";
snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str());
s += buf;
s += dump_headers(res.headers);
s += "\n";
if (!res.body.empty())
{
s += res.body;
}
s += "\n";
return s;
}
void failed(Response &res, int status, const std::string &msg)
{
res.status = status;
char response_msg[msg.length() + strlen(FAILURE_MSG) + 2];
snprintf(response_msg, sizeof(response_msg), "{ \"result\": { \"success\": false, \"error\": \"%s\" } }", msg.c_str());
res.set_content(response_msg, strlen(response_msg), "application/json");
return;
}
void bad_request(Response &res, const std::string &msg)
{
failed(res, 200, msg);
return;
}
void success(Response &res)
{
res.status = 200;
res.set_content(SUCCESS_MSG, SUCCESS_MSG_LEN, "application/json");
return;
}
static RemoteClient *GetRemoteClient(HostInfo *host_info)
{
RemoteClient *tmp_client = nullptr;
if (host_info->type == CLIENT_TYPE_HTTP_SERVER)
{
if (host_info->http_server_type.compare(HTTP_SERVER_ARCHIVEORG))
{
tmp_client = new ArchiveOrgClient();
}
else if (host_info->http_server_type.compare(HTTP_SERVER_APACHE))
{
tmp_client = new BaseClient();
}
else if (host_info->http_server_type.compare(HTTP_SERVER_MS_IIS))
{
tmp_client = new BaseClient();
}
else if (host_info->http_server_type.compare(HTTP_SERVER_NGINX))
{
tmp_client = new BaseClient();
}
else if (host_info->http_server_type.compare(HTTP_SERVER_RCLONE))
{
tmp_client = new BaseClient();
}
else if (host_info->http_server_type.compare(HTTP_SERVER_NPX_SERVE))
{
tmp_client = new BaseClient();
}
}
else if (host_info->type == CLIENT_TYPE_WEBDAV)
{
tmp_client = new WebDAVClient();
}
else if (host_info->type == CLIENT_TYPE_FILEHOST)
{
tmp_client = new BaseClient();
}
else if (host_info->type == CLIENT_TYPE_SMB)
{
tmp_client = new SmbClient();
}
else if (host_info->type == CLIENT_TYPE_SFTP)
{
tmp_client = new SFTPClient();
}
else if (host_info->type == CLIENT_TYPE_NFS)
{
tmp_client = new NfsClient();
}
else if (host_info->type == CLIENT_TYPE_FTP)
{
tmp_client = new FtpClient();
FtpClient *ftp_client = (FtpClient*) tmp_client;
ftp_client->SetCallbackXferFunction(FtpCallback);
}
if (tmp_client != nullptr)
tmp_client->Connect(host_info->url, host_info->username, host_info->password);
return tmp_client;
}
static void DeleteRemoteClient(RemoteClient *tmp_client)
{
tmp_client->Quit();
delete tmp_client;
}
void *DownloadFilesThread(void *argp)
{
char temp_file[2049];
uint64_t tmp_file_size;
int ret;
while (true)
{
for (int i=0; i < bg_download_list.size(); i++)
{
if (bg_download_list[i].state == STATE_PENDING)
{
RemoteClient *tmp_client = GetRemoteClient(&(bg_download_list[i].host_info));
g_bytes_transfered = &(bg_download_list[i].bytes_transfered);
if (bg_download_list[i].host_info.type == CLIENT_TYPE_FTP)
{
FtpClient *ftpclient = (FtpClient*)tmp_client;
g_dl_offset = 0;
ftpclient->SetCallbackBytes(1);
ftpclient->SetCallbackXferFunction(DownloadFtpCallback);
}
bg_download_list[i].state = STATE_DOWNLOADING;
CONFIG::SaveBgDownloadData();
snprintf(temp_file, sizeof(temp_file), "%s.tmp", bg_download_list[i].dest_path.c_str());
Util::Notify("Started download %s", bg_download_list[i].dest_path.c_str());
ret = tmp_client->Get(temp_file, bg_download_list[i].src_path);
FS::Rename(temp_file, bg_download_list[i].dest_path);
if (ret == 0)
{
bg_download_list[i].state = STATE_FAILED;
Util::Notify("Failed to download %s", bg_download_list[i].dest_path.c_str());
}
else
{
Util::Notify("Completed download %s", bg_download_list[i].dest_path.c_str());
bg_download_list[i].state = STATE_SUCCESS;
}
CONFIG::SaveBgDownloadData();
DeleteRemoteClient(tmp_client);
}
else if (bg_download_list[i].state == STATE_DOWNLOADING)
{
// Resume interrupted download
RemoteClient *tmp_client = GetRemoteClient(&(bg_download_list[i].host_info));
g_bytes_transfered = &(bg_download_list[i].bytes_transfered);
if (bg_download_list[i].host_info.type == CLIENT_TYPE_FTP)
{
FtpClient *ftpclient = (FtpClient*)tmp_client;
ftpclient->SetCallbackBytes(1);
ftpclient->SetCallbackXferFunction(DownloadFtpCallback);
}
bg_download_list[i].state = STATE_RESUMED;
snprintf(temp_file, sizeof(temp_file), "%s.tmp", bg_download_list[i].dest_path.c_str());
// Check if temp file still exists, if exists then resume download
Util::Notify("Resuming download %s", bg_download_list[i].dest_path.c_str());
if (FS::FileExists(temp_file))
{
tmp_file_size = FS::GetSize(temp_file);
g_dl_offset = tmp_file_size;
ret = tmp_client->Get(temp_file, bg_download_list[i].src_path, tmp_file_size);
}
else
{
g_dl_offset = 0;
ret = tmp_client->Get(temp_file, bg_download_list[i].src_path);
}
FS::Rename(temp_file, bg_download_list[i].dest_path);
if (ret == 0)
{
bg_download_list[i].state = STATE_FAILED;
Util::Notify("Failed to download %s", bg_download_list[i].dest_path.c_str());
}
else
{
Util::Notify("Completed download %s", bg_download_list[i].dest_path.c_str());
bg_download_list[i].state = STATE_SUCCESS;
}
CONFIG::SaveBgDownloadData();
DeleteRemoteClient(tmp_client);
}
}
sleep(1);
}
return nullptr;
}
void *ServerThread(void *argp)
{
svr->Get("/", [&](const Request &req, Response &res)
{
res.set_redirect("/index.html");
});
svr->Post("/store_bg_install_data", [&](const Request &req, Response &res)
{
const char *hash_param;
const char *path_param;
const char *url_param;
const char *username_param;
const char *password_param;
const char *http_server_type_param;
int type_param;
json_object *jobj = json_tokener_parse(req.body.c_str());
if (jobj != nullptr)
{
hash_param = json_object_get_string(json_object_object_get(jobj, "hash"));
url_param = json_object_get_string(json_object_object_get(jobj, "url"));
path_param = json_object_get_string(json_object_object_get(jobj, "path"));
username_param = json_object_get_string(json_object_object_get(jobj, "username"));
password_param = json_object_get_string(json_object_object_get(jobj, "password"));
http_server_type_param = json_object_get_string(json_object_object_get(jobj, "http_server_type"));
type_param = json_object_get_int(json_object_object_get(jobj, "type"));
if (url_param == nullptr || hash_param == nullptr)
{
bad_request(res, "Required url_param or hash parameter missing");
return;
}
PackageInstallData pkg_data;
pkg_data.host_info.url = url_param;
if (username_param != nullptr)
pkg_data.host_info.username = username_param;
if (password_param != nullptr)
pkg_data.host_info.password = password_param;
if (path_param != nullptr)
pkg_data.path = path_param;
if (http_server_type_param != nullptr)
pkg_data.host_info.http_server_type = http_server_type_param;
pkg_data.timestamp = Util::GetTick();
pkg_data.host_info.type = type_param;
pkg_data.client = nullptr;
CONFIG::AddPackageInstallHostData(hash_param, pkg_data);
CONFIG::SavePackageInstallHostData();
}
});
svr->Get("/ls", [&](const Request &req, Response &res)
{
std::vector<std::string> files = FS::ListFiles("/");
std::string out;
for (int i=0; i < files.size(); i++)
{
out.append(files[i]).append("\n");
}
res.set_content(out, "text/plain");
});
svr->Get("/bg_install/(.*)", [&](const Request &req, Response &res)
{
std::string hash = req.matches[1];
PackageInstallData* pkg_host_data = CONFIG::GetPackageInstallHostData(hash);
RemoteClient *tmp_client;
if (pkg_host_data == nullptr)
{
failed(res, 500, "Cannot resume background install of " + hash + ". Host data not found.");
return;
}
tmp_client = GetRemoteClient(&(pkg_host_data->host_info));
if (tmp_client == nullptr)
{
res.status = 500;
return;
}
std::string path = pkg_host_data->path;
/*
if (req.method == "HEAD")
{
int64_t file_size;
int ret;
res.status = 204;
res.set_header("Content-Length", std::to_string(pkg_host_data->file_size));
res.set_header("Accept-Ranges", "bytes");
DeleteRemoteClient(tmp_client);
return;
}
*/
if (req.ranges.empty())
{
res.status = 200;
res.set_content_provider(
(1024*128), "application/octet-stream",
[tmp_client, path](size_t offset, size_t length, DataSink &sink) {
int ret = tmp_client->GetRange(path, sink, length, offset);
return (ret == 1);
},
[tmp_client, path](bool success) {
DeleteRemoteClient(tmp_client);
});
}
else
{
res.status = 206;
size_t range_len = (req.ranges[0].second - req.ranges[0].first) + 1;
if (req.ranges[0].second >= 18000000000000000000ul)
{
range_len = PKG_INITIAL_REQUEST_SIZE;
res.set_header("Content-Length", std::to_string(range_len));
res.set_header("Content-Range", std::string("bytes ") + std::to_string(req.ranges[0].first)+"-" + std::to_string(req.ranges[0].first+PKG_INITIAL_REQUEST_SIZE-1) + "/"+std::to_string(range_len));
}
std::pair<ssize_t, ssize_t> range = req.ranges[0];
res.set_content_provider(
range_len, "application/octet-stream",
[tmp_client, path, range, range_len](size_t offset, size_t length, DataSink &sink) {
int ret;
ret = tmp_client->GetRange(path, sink, range_len, range.first);
return (ret==1);
},
[tmp_client, path, range](bool success) {
DeleteRemoteClient(tmp_client);
});
}
});
svr->Post("/download_url", [&](const Request &req, Response &res)
{
int type_param;
const char *url_param;
const char *username_param;
const char *password_param;
const char *http_server_type_param;
const char *src_path_param;
const char *dest_path_param;
uint64_t file_size_param;
uint64_t id_param;
json_object *jobj = json_tokener_parse(req.body.c_str());
if (jobj != nullptr)
{
type_param = json_object_get_int(json_object_object_get(jobj, "type"));
url_param = json_object_get_string(json_object_object_get(jobj, "url"));
username_param = json_object_get_string(json_object_object_get(jobj, "username"));
password_param = json_object_get_string(json_object_object_get(jobj, "password"));
http_server_type_param = json_object_get_string(json_object_object_get(jobj, "http_server_type"));
src_path_param = json_object_get_string(json_object_object_get(jobj, "src_path"));
dest_path_param = json_object_get_string(json_object_object_get(jobj, "dest_path"));
file_size_param = json_object_get_uint64(json_object_object_get(jobj, "size"));
id_param = json_object_get_uint64(json_object_object_get(jobj, "id"));
if (url_param == nullptr || src_path_param == nullptr || dest_path_param == nullptr)
{
bad_request(res, "Required parameters are missing");
return;
}
BgDownloadData download_data;
download_data.host_info.type = type_param;
download_data.host_info.url = url_param;
download_data.src_path = src_path_param;
download_data.dest_path = dest_path_param;
download_data.file_size = file_size_param;
download_data.state = STATE_PENDING;
download_data.id = id_param;
download_data.bytes_transfered = 0;
download_data.timestamp = Util::GetTick();
if (username_param != nullptr)
download_data.host_info.username = username_param;
if (password_param != nullptr)
download_data.host_info.password = password_param;
if (http_server_type_param != nullptr)
download_data.host_info.http_server_type = http_server_type_param;
CONFIG::AddBgDownloadData(download_data);
CONFIG::SaveBgDownloadData();
}
});
svr->Get("/get_download_state", [&](const Request &req, Response &res)
{
json_object *download_list = json_object_new_array();
for (int i=0; i < bg_download_list.size(); i++)
{
json_object *download_item_obj = json_object_new_object();
json_object_object_add(download_item_obj, "path", json_object_new_string(bg_download_list[i].dest_path.c_str()));
json_object_object_add(download_item_obj, "bytes_transfered", json_object_new_uint64(bg_download_list[i].bytes_transfered));
json_object_object_add(download_item_obj, "file_size", json_object_new_uint64(bg_download_list[i].file_size));
json_object_object_add(download_item_obj, "state", json_object_new_int(bg_download_list[i].state));
json_object_object_add(download_item_obj, "timestamp", json_object_new_uint64(bg_download_list[i].timestamp/1000000));
json_object_array_add(download_list, download_item_obj);
}
const char *payload_str = json_object_to_json_string(download_list);
res.status = 200;
res.set_content(payload_str, "application/json");
});
svr->Get("/stop", [&](const Request & /*req*/, Response & /*res*/)
{
svr->stop();
});
svr->Get("/version", [&](const Request & req, Response &res)
{
res.status = 200;
char version[20];
sprintf(version, "%.2f", EZREMOTE_VERSION);
res.set_content(version, "text/html");
});
svr->set_error_handler([](const Request & /*req*/, Response &res)
{
const char *fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), fmt, res.status);
res.set_content(buf, "text/html");
});
/*
svr->set_logger([](const Request &req, const Response &res)
{
dbglogger_log("%s", log(req, res).c_str());
});
*/
svr->set_payload_max_length(1024 * 1024 * 12);
svr->set_tcp_nodelay(true);
svr->set_mount_point("/", "/");
svr->listen("0.0.0.0", http_server_port);
return NULL;
}
void Start()
{
if (svr == nullptr)
svr = new Server();
if (!svr->is_valid())
{
return;
}
Util::Notify("Starting ezRemote Server %.2f on port %d", EZREMOTE_VERSION, http_server_port);
ServerThread(nullptr);
}
void Stop()
{
if (svr != nullptr)
svr->stop();
}
void StartDownloadThread()
{
pthread_create(&bg_download_thread, NULL, DownloadFilesThread, NULL);
}
bool IsStarted()
{
httplib::Client client = httplib::Client("http://127.0.0.1:6701");
if (auto res = client.Get("/version"))
{
return true;
}
return false;
}
}