#include "SyncFileReceiver.h" #include #include #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; TempFileManager FileManager; std::chrono::seconds FileTimeout = 300s, FileMaxProcTimeout = 600s; }; SyncFileReceiver::SyncFileReceiver() : pimpl(std::make_unique()) { } SyncFileReceiver::~SyncFileReceiver() = default; // 【修改】虚函数的默认实现:如果子类不重写,则默认返回原先的成功状态(201 Created) uns::ResponsePtr SyncFileReceiver::ProcessFiles(TempFileManager& file_info, SFR_FileMap file_map, const std::string& tmp_root, uns::RequestPtr request) { SCLOGF_ERROR("SyncFileReceiver::ProcessFiles default handler triggered. Files count: {}", file_map.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; SCLOGF_DEBUG("SyncFileReceiver init mode: {}", (html ? "html" : "json")); } void SyncFileReceiver::SetCORSEnable(bool enable) { pimpl->EnableCORS = enable; SCLOGF_DEBUG("SyncFileReceiver CORS mode: {}", (enable ? "enabled" : "disabled")); } void SyncFileReceiver::SetTempRoot(std::string temp_root) { pimpl->TempRoot = temp_root; if (pimpl->FileManager.SetBaseDirectory(temp_root)) SCLOGF_TRACE("SFR-TempRoot: {}", pimpl->TempRoot); else SCLOGF_WARNING("SFR: Failed to Set Temp Root ({})", temp_root); 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(); SCLOGF_INFO("IP: [{}] has been blocked untill {{{}}}", ip, std::string(expr_time)); 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()) { SCLOGF_WARNING("Failed to write file [{}]: can't open stream", path); return false; } stream.write(bytes.data(), bytes.size()); bool fail = stream.fail(); if (fail) SCLOGF_WARNING("Failed to write file [{}]: can't write to stream", path); else SCLOGF_TRACE("Wrote {siz-b} to file [{}]", bytes.size(), path); return !fail; } void SyncFileReceiver::SetFileTimeout(std::chrono::seconds timeout, std::chrono::seconds max_proc_timeout) noexcept { if (timeout.count() > 0) pimpl->FileTimeout = timeout; if (max_proc_timeout.count() > 0) pimpl->FileMaxProcTimeout = max_proc_timeout; } uns::PathTraversalDefenceLevel SyncFileReceiver::PTDefence() { return uns::PathTraversalDefenceLevel::DenyAll; } bool SyncFileReceiver::IsPathSafe(const std::string& raw_path) { return false; } bool SyncFileReceiver::IsHeaderValid(uns::RequestPtr request) { return true; } std::string SyncFileReceiver::EncodeUploadResult() { try { Json::Value root; Json::FastWriter writer; // 1. 从管理器安全获取当前所有文件的快照 auto file_infos = pimpl->FileManager.GetAllFileInfos(); // 2. 组装 JSON 数据 root["AcceptedCount"] = static_cast(file_infos.size()); root["AcceptedFiles"] = Json::Value(Json::arrayValue); for (const auto& ele : file_infos) { Json::Value sub; sub["FileName"] = ele.GetStorageFileName(); sub["UploadTime"] = ele.GetUploadTime().GetTimeStamp(); root["AcceptedFiles"].append(sub); } return writer.write(root); } catch (...) { // 极致异常安全兜底:如果 Json 报错或内存写满,返回一个合法的空 JSON 字符串 return "{\"AcceptedCount\":0,\"AcceptedFiles\":[]}"; } } std::string SyncFileReceiver::EncodeUploadResultHTML() { const char* html = R"( Upload Result

Upload Result


AcceptedCount: %lld

AcceptedFiles:
%s

)"; try { // 1. 获取文件快照 auto file_infos = pimpl->FileManager.GetAllFileInfos(); // 2. 拼接文件列表 HTML std::string tmp; for (const auto& ele : file_infos) tmp += "[" + ele.GetStorageFileName() + "] - {" + ele.GetUploadTime().Format("%Y-%m-%d %H:%M:%S") + "}
"; // 3. 动态安全计算所需缓冲区大小(32字节用于容纳 %lld 的数字展开) size_t html_size = strlen(html) + tmp.size() + 32; // 利用 std::string 管理缓冲区内存(RAII 机制,无论发生什么都会自动释放,绝不泄漏) std::string result_str(html_size, '\0'); // 使用安全的 snprintf 写入 string 内部缓冲区 int written = snprintf(result_str.data(), result_str.size(), html, static_cast(file_infos.size()), tmp.c_str()); if (written > 0) { result_str.resize(written); // 裁剪掉尾部多余的 \0 return result_str; } return "HTML generation failed"; } catch (...) { // 异常安全兜底 return "

Upload Result Error

"; } } 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); SCLOGF_DEBUG("Request recived, ip: [{}], method: {}", req_ip, request->GetImpl()->webcc_req->method()); // 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 tmp_status = uns::ConvertStatus(PreCheckRequest(request)); if (tmp_status != webcc::kOK) return (pimpl->EnableCORS ? uns::ResponseBuilder().Code(tmp_status).EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Code(tmp_status).EmptyBody()()); else if (!request->IsForm()) return (pimpl->EnableCORS ? uns::ResponseBuilder().RequestFormatError().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().RequestFormatError().EmptyBody()()); else { SFR_FileMap fmap; 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()); fmap.insert({ form->GetFileNameS(), info.GetStorageFileName() }); //pimpl->FileInfo.push_back(info); if (!pimpl->FileManager.RegisterFile(info, pimpl->FileTimeout, pimpl->FileMaxProcTimeout)) SCLOGF_WARNING("FileManager.RegisterFile Error, File: {}, TempRoot: {}", form->GetFileName(), pimpl->TempRoot); } // 同步调用虚函数,获取具体的业务处理结果(及构筑好的自定义 HTTP Response) uns::ResponsePtr response = ProcessFiles(pimpl->FileManager, fmap, pimpl->TempRoot, request); return response; } } else return (pimpl->EnableCORS ? uns::ResponseBuilder().IllegalUpload().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().IllegalUpload().EmptyBody()()); }