Files
UNSWebServerCore_LinuxSO/SyncFileReceiver.cpp
T
2026-06-30 17:49:51 +08:00

255 lines
8.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "SyncFileReceiver.h"
#include <cstring>
#include <json/json.h>
#include "ServerLogger.h"
#include "PathTraversal.h"
#include "HTTPObjectsBridge.h"
#include "UNSResponseBuilder.h"
class SyncFileReceiver::Impl
{
public:
bool EnableCORS = false;
bool HTMLResponse = false;
std::string TempRoot;
IPTablePtr BlockedIPs = nullptr;
WebFileInfoVec FileInfo;
};
SyncFileReceiver::SyncFileReceiver() : pimpl(std::make_unique<Impl>())
{
}
SyncFileReceiver::~SyncFileReceiver() = default;
// 【修改】虚函数的默认实现:如果子类不重写,则默认返回原先的成功状态(201 Created)
uns::ResponsePtr SyncFileReceiver::ProcessFiles(const WebFileInfoVec& file_info, const std::string& tmp_root, uns::RequestPtr request)
{
SCLOGF_ERROR("SyncFileReceiver::ProcessFiles default handler triggered. Files count: {}", file_info.size());
std::string resp_body = (pimpl->HTMLResponse ? EncodeUploadResultHTML() : EncodeUploadResult());
return (pimpl->EnableCORS ? uns::ResponseBuilder().Created().Body(resp_body).AutoCORS(request)() : uns::ResponseBuilder().Created().Body(resp_body)());
}
void SyncFileReceiver::SetResponseMode(bool html)
{
pimpl->HTMLResponse = html;
SCLOG_DEBUG("SyncFileReceiver init mode: %s", (html ? "html" : "json"));
}
void SyncFileReceiver::SetCORSEnable(bool enable)
{
pimpl->EnableCORS = enable;
SCLOG_DEBUG("SyncFileReceiver CORS mode: %s", (enable ? "enabled" : "disabled"));
}
void SyncFileReceiver::SetTempRoot(std::string temp_root)
{
pimpl->TempRoot = temp_root;
SCLOG_TRACE("SFR-TempRoot: %s", pimpl->TempRoot.c_str());
return;
}
void SyncFileReceiver::UpdateBlockedIPs(IPTablePtr ip)
{
pimpl->BlockedIPs = ip;
return;
}
void SyncFileReceiver::AppenedBlockedIP(DateTime::Span block_time, std::string ip)
{
DateTime expr_time = (DateTime::Now() += block_time);
IPList li{ ip };
pimpl->BlockedIPs->Appened(expr_time, li);
pimpl->BlockedIPs->Update();
SCLOG_INFO("IP: [%s] has been blocked untill {%s}", ip.c_str(), std::string(expr_time).c_str());
return;
}
uns::HTTPMethod SyncFileReceiver::GetMethod(uns::RequestPtr request)
{
std::string method = request->GetImpl()->webcc_req->method();
if (method == "GET")
return uns::HTTPMethod::H_GET;
else if (method == "PUT")
return uns::HTTPMethod::H_PUT;
else if (method == "POST")
return uns::HTTPMethod::H_POST;
else if (method == "HEAD")
return uns::HTTPMethod::H_HEAD;
else if (method == "TRACE")
return uns::HTTPMethod::H_TRACE;
else if (method == "PATCH")
return uns::HTTPMethod::H_PATCH;
else if (method == "DELETE")
return uns::HTTPMethod::H_DELETE;
else if (method == "OPTIONS")
return uns::HTTPMethod::H_OPTIONS;
else if (method == "CONNECT")
return uns::HTTPMethod::H_CONNECT;
else
return uns::HTTPMethod::H_UNKNOWN;
}
bool SyncFileReceiver::WriteFile(const std::string& path, const std::string& bytes)
{
std::ofstream stream{ path, std::ios::binary };
if (stream.fail())
{
SCLOG_WARNING("Failed to write file [%s]: can't open stream", path.c_str());
return false;
}
stream.write(bytes.data(), bytes.size());
if (stream.fail())
SCLOG_WARNING("Failed to write file [%s]: can't write to stream", path.c_str());
return !stream.fail();
}
uns::PathTraversalDefenceLevel SyncFileReceiver::PTDefence()
{
return uns::PathTraversalDefenceLevel::DenyAll;
}
bool SyncFileReceiver::IsPathSafe(const std::string & raw_path)
{
return false;
}
std::string SyncFileReceiver::EncodeUploadResult()
{
Json::Value root;
Json::FastWriter writer;
root["AcceptedCount"] = pimpl->FileInfo.size();
root["AcceptedFiles"] = Json::Value(Json::arrayValue);
for (auto& ele : pimpl->FileInfo)
{
Json::Value sub;
sub["FileName"] = ele.GetStorageFileName();
sub["UploadTime"] = ele.GetUploadTime().GetTimeStamp();
root["AcceptedFiles"].append(sub);
}
return writer.write(root);
}
std::string SyncFileReceiver::EncodeUploadResultHTML()
{
const char* html = R"(
<html>
<head>
<meta charset="utf-8"/>
<title>Upload Result</title>
</head>
<body>
<center>
<h1>Upload Result</h1>
<hr/>
<p>AcceptedCount: %lld</p>
<p>AcceptedFiles: <br>%s</p>
</center>
</body>
</html>
)";
std::string tmp;
for (auto& ele : pimpl->FileInfo)
tmp += "[" + ele.GetStorageFileName() + "] - {" + ele.GetUploadTime().Format("%Y-%m-%d %H:%M:%S") + "}<br>";
size_t html_size = strlen(html) + tmp.size() + 10;
char* result = new char[html_size];
memset(result, 0, sizeof(result));
sprintf(result, html, pimpl->FileInfo.size(), tmp.c_str());
tmp = std::string(result);
delete[] result;
return tmp;
}
uns::ResponsePtr SyncFileReceiver::Execute(uns::RequestPtr request)
{
uns::HTTPMethod method = GetMethod(request);
std::string x_real_ip;
if(request->GetImpl()->webcc_req->HasHeader("X-Real-IP"))
x_real_ip = request->GetImpl()->webcc_req->GetHeader("X-Real-IP");
std::string req_ip = (x_real_ip.empty() ? request->GetImpl()->webcc_req->address() : x_real_ip);
SCLOG_DEBUG("Request recived, ip: [%s], method: %s", req_ip.c_str(), request->GetImpl()->webcc_req->method().c_str());
// path test
std::string path = request->GetImpl()->webcc_req->url().path();
auto status = PathTraversal::AnalyzeUrlTraversal(path);
if(status != PathTraversal::UrlSafetyStatus::Safe)
SCLOGF_WARNING("PathTraversal Detected: {}, Level: {}", path, PathTraversal::ToString(status));
switch(PTDefence())
{
case uns::PathTraversalDefenceLevel::DenyAll:
if(status != PathTraversal::UrlSafetyStatus::Safe)
return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()());
break;
case uns::PathTraversalDefenceLevel::AutoNormalize:
{
if(status == PathTraversal::UrlSafetyStatus::EvasiveTraversal)
return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()());
auto decoded_path = PathTraversal::UrlDecode(path);
if(!IsPathSafe(decoded_path))
return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()());
auto url = request->GetImpl()->webcc_req->url();
url.ForceSet_Path(PathTraversal::NormalizeUrlPath(decoded_path));
request->GetImpl()->webcc_req->set_url(std::move(url));
break;
}
case uns::PathTraversalDefenceLevel::AllowNormal:
{
if(status == PathTraversal::UrlSafetyStatus::EvasiveTraversal)
return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()());
auto decoded_path = PathTraversal::UrlDecode(path);
if(!IsPathSafe(decoded_path))
return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()());
auto url = request->GetImpl()->webcc_req->url();
url.ForceSet_Path(decoded_path);
request->GetImpl()->webcc_req->set_url(std::move(url));
break;
}
default:
break; //Check Bypassed by [AllowAll]
}
if ((method & uns::H_PUT) || (method & uns::H_POST))
{
if (pimpl->BlockedIPs != nullptr)
{
std::string req_ip = request->GetImpl()->webcc_req->address();
pimpl->BlockedIPs->Update();
if (pimpl->BlockedIPs->IPExist(req_ip))
return (pimpl->EnableCORS ? uns::ResponseBuilder().IPBlocked().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().IPBlocked().EmptyBody()());
}
webcc::Status tmpStatus = uns::ConvertStatus(PreCheckRequest(request));
if (tmpStatus != webcc::kOK)
return (pimpl->EnableCORS ? uns::ResponseBuilder().Code(tmpStatus).EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Code(tmpStatus).EmptyBody()());
else if (!request->IsForm())
return (pimpl->EnableCORS ? uns::ResponseBuilder().RequestFormatError().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().RequestFormatError().EmptyBody()());
else
{
for (auto& form : request->GetFormParts())
{
if (form->GetFileName().empty())
continue;
if (!PreCheckForm(form))
{
SCLOGF_DEBUG("File denied: [{}], {} bytes", form->GetFileName(), form->GetDataSize());
continue;
}
SCLOGF_DEBUG("File recived: [{}], {} bytes", form->GetFileName(), form->GetDataSize());
WebFileInfo info(form->GetFileNameS(), form->GetDataSize());
WriteFile(info.MakePath(pimpl->TempRoot), form->GetData());
pimpl->FileInfo.push_back(info);
}
// 【修改的关键步骤】
// 1. 同步调用虚函数,获取具体的业务处理结果(及构筑好的自定义 HTTP Response)
uns::ResponsePtr response = ProcessFiles(pimpl->FileInfo, pimpl->TempRoot, request);
// 2. 清理当前类中的文件缓存(防止污染下一次 HTTP 请求)
pimpl->FileInfo.clear();
// 3. 作为最后一步直接返回
return response;
}
}
else
return (pimpl->EnableCORS ? uns::ResponseBuilder().IllegalUpload().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().IllegalUpload().EmptyBody()());
}