upload
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
#include "FileReceiver.h"
|
||||
#include <cstring>
|
||||
#include <json/json.h>
|
||||
#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<Impl>())
|
||||
{
|
||||
}
|
||||
|
||||
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"(
|
||||
<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 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()());
|
||||
}
|
||||
Reference in New Issue
Block a user