#include "FileReceiver.h" #include #include #include "ServerLogger.h" #include "PathTraversal.h" #include "HTTPObjectsBridge.h" #include "UNSResponseBuilder.h" class FileReceiver::Impl { public: bool EnableCORS = false; bool HTMLResponse = false; std::string TempRoot; IPTablePtr BlockedIPs = nullptr; WebFileInfoVec FileInfo; FileProcessorCallback Callback = nullptr; }; FileReceiver::FileReceiver() : pimpl(std::make_unique()) { } FileReceiver::~FileReceiver() = default; bool FileReceiver::CallFileProcesser() { if (pimpl->Callback == nullptr) return false; std::thread thFileProcesser(pimpl->Callback, pimpl->FileInfo, pimpl->TempRoot); //Use copy construst to avoid repeat data process. if (!thFileProcesser.joinable()) return false; thFileProcesser.detach(); pimpl->FileInfo.clear(); SCLOGF_TRACE("FileProcesser Function (Address: {}) Started.", pimpl->Callback); return true; } void FileReceiver::SetResponseMode(bool html) { pimpl->HTMLResponse = html; SCLOG_DEBUG("FileReceiver init mode: %s", (html ? "html" : "json")); } void FileReceiver::SetCORSEnable(bool enable) { pimpl->EnableCORS = enable; SCLOG_DEBUG("FileReceiver CORS mode: %s", (enable ? "enabled" : "disabled")); } void FileReceiver::SetTempRoot(std::string temp_root) { pimpl->TempRoot = temp_root; SCLOG_TRACE("FR-TempRoot: %s", pimpl->TempRoot.c_str()); return; } void FileReceiver::SetFileCallback(FileProcessorCallback fpcb) { pimpl->Callback = fpcb; return; } void FileReceiver::UpdateBlockedIPs(IPTablePtr ip) { pimpl->BlockedIPs = ip; return; } void FileReceiver::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 FileReceiver::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 FileReceiver::WriteFile(const std::string& path, const std::string& bytes) { //Code from WebCC source (commit 554470ac65f9fa08b53ee9adeb819ae7c70ef698). 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 FileReceiver::PTDefence() { return uns::PathTraversalDefenceLevel::DenyAll; } bool FileReceiver::IsPathSafe(const std::string& raw_path) { // 无外部重载时认为所有路径均不能通过检查 return false; } std::string FileReceiver::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 FileReceiver::EncodeUploadResultHTML() { const char* html = R"( Upload Result

Upload Result


AcceptedCount: %lld

AcceptedFiles:
%s

)"; std::string tmp; for (auto& ele : pimpl->FileInfo) tmp += "[" + ele.GetStorageFileName() + "] - {" + ele.GetUploadTime().Format("%Y-%m-%d %H:%M:%S") + "}
"; 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 FileReceiver::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->GetImpl()->webcc_req->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)) 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); } std::string resp_body = (pimpl->HTMLResponse ? EncodeUploadResultHTML() : EncodeUploadResult()); CallFileProcesser(); return (pimpl->EnableCORS ? uns::ResponseBuilder().Created().Body(resp_body).AutoCORS(request)() : uns::ResponseBuilder().Created().Body(resp_body)()); } } else return (pimpl->EnableCORS ? uns::ResponseBuilder().IllegalUpload().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().IllegalUpload().EmptyBody()()); }