调整类重载,修复部分BUG,增加请求头检查
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
// CoreDeploy.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
|
||||
//
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
|
||||
#include "../UNSWebServerCore/ServerLogger.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static const fs::path src = "F:\\UNSWebServerCore\\UNSWebServerCore\\";
|
||||
|
||||
/**
|
||||
* @brief 查找指定目录下所有包含 UNSWSC_DLL_EXPORT 宏的 .h 头文件
|
||||
*
|
||||
* @param source_dir 目标源代码文件夹路径
|
||||
* @return std::vector<std::string> 匹配到的文件名列表(纯文件名,不含路径)
|
||||
*/
|
||||
std::vector<std::string> FileExportHeaders(const std::string& source_dir)
|
||||
{
|
||||
std::vector<std::string> matching_files;
|
||||
const std::string target_macro = "UNSWSC_DLL_EXPORT";
|
||||
|
||||
fs::path dir_path(source_dir);
|
||||
|
||||
// 校验路径有效性
|
||||
std::error_code ec;
|
||||
if (!fs::exists(dir_path, ec) || !fs::is_directory(dir_path, ec))
|
||||
return matching_files;
|
||||
|
||||
// 递归遍历目录(跳过因权限不足无法访问的文件夹)
|
||||
auto options = fs::directory_options::skip_permission_denied;
|
||||
for (const auto& entry : fs::recursive_directory_iterator(dir_path, options, ec))
|
||||
{
|
||||
// 1. 过滤:必须是普通文件且扩展名为 .h
|
||||
if (entry.is_regular_file(ec) && entry.path().extension() == ".h")
|
||||
{
|
||||
std::ifstream file(entry.path(), std::ios::in);
|
||||
if (!file.is_open())
|
||||
continue;
|
||||
|
||||
// 2. 逐行读取并搜索关键字
|
||||
std::string line;
|
||||
while (std::getline(file, line))
|
||||
{
|
||||
if (line.find(target_macro) != std::string::npos)
|
||||
{
|
||||
// 找到匹配项,提取纯文件名并加入列表
|
||||
matching_files.push_back(entry.path().filename().string());
|
||||
break; // 找到后跳出当前文件的读取,避免重复添加
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matching_files;
|
||||
}
|
||||
|
||||
std::map<std::string, std::map<bool, std::vector<std::string>>> lib_files =
|
||||
{
|
||||
{
|
||||
"Debug",
|
||||
{
|
||||
{ true, { "UNSWebServerCored.lib" } },
|
||||
{ false, { "UNSWebServerCored.dll", "UNSWebServerCored.pdb" } }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Release",
|
||||
{
|
||||
{ true, { "UNSWebServerCore.lib" } },
|
||||
{ false, { "UNSWebServerCore.dll", "UNSWebServerCore.pdb" } }
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
bool CopyHeaderFile(const fs::path& dest, const std::vector<std::string>& files)
|
||||
{
|
||||
SCLOGF_INFO("Header Copy Begin: {} -> {}", src, dest);
|
||||
for (const auto& file : files)
|
||||
{
|
||||
std::error_code ec;
|
||||
fs::copy_file((src / file), (dest / file), fs::copy_options::overwrite_existing, ec);
|
||||
if (ec)
|
||||
{
|
||||
SCLOGF_ERROR("Header [{}] Copy Failed: {} ({})", file, ec.value(), ec.message());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
SCLOGF_INFO("Header Copy Success, All File Copied To {}", dest);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CopyLibraryFile(const fs::path& dest, const std::string& arch_text)
|
||||
{
|
||||
SCLOGF_INFO("Library Copy Begin: {} -> {}", src, dest);
|
||||
for (const auto& [build, f_info] : lib_files)
|
||||
{
|
||||
fs::path dst_build_path = ((dest / "..") / arch_text) / build;
|
||||
for (const auto& [lib, files] : f_info)
|
||||
{
|
||||
fs::path dst_path = (lib ? dest : dst_build_path);
|
||||
for (const auto& file : files)
|
||||
{
|
||||
std::error_code ec;
|
||||
fs::path file_path = (((src / "..") / arch_text) / build) / file;
|
||||
fs::copy_file(file_path, (dst_path / file), fs::copy_options::overwrite_existing, ec);
|
||||
if (ec)
|
||||
{
|
||||
SCLOGF_ERROR("Library [{}] Copy Failed: {} ({})", file, ec.value(), ec.message());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SCLOGF_INFO("Library Copy Success, All File Copied To {}", dest);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeployCore(const fs::path& header_path, const fs::path& library_path = "")
|
||||
{
|
||||
if (header_path.empty())
|
||||
{
|
||||
SCLOGF_FATAL("ARG ERROR: Header IS Empty");
|
||||
return false;
|
||||
}
|
||||
auto header_files = FileExportHeaders(src.string());
|
||||
if (!CopyHeaderFile(header_path, header_files))
|
||||
{
|
||||
SCLOGF_FATAL("Header Copy Failed.");
|
||||
return false;
|
||||
}
|
||||
if (!CopyLibraryFile((library_path.empty() ? header_path : library_path), "x64"))
|
||||
{
|
||||
SCLOGF_FATAL("Library Copy Failed.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
SCLOG_CONSOLE_INIT(uns::llAll);
|
||||
|
||||
DeployCore("F:\\SVProjects\\SVUpdate\\OTAServer\\unswsc", "F:\\SVProjects\\SVUpdate\\OTAServer");
|
||||
|
||||
SCLOG_CLOSE();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>18.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{71d27301-6fd9-456f-956c-a52ce5e12675}</ProjectGuid>
|
||||
<RootNamespace>CoreDeploy</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<EnableSegmentHeap>true</EnableSegmentHeap>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<EnableSegmentHeap>true</EnableSegmentHeap>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<EnableSegmentHeap>true</EnableSegmentHeap>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;_WINDOWS;UNSWEBSERVERCORE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<EnableSegmentHeap>true</EnableSegmentHeap>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\UNSWebServerCore\LogArg.cpp" />
|
||||
<ClCompile Include="..\UNSWebServerCore\ServerLogger.cpp" />
|
||||
<ClCompile Include="CoreDeploy.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\UNSWebServerCore\Export.h" />
|
||||
<ClInclude Include="..\UNSWebServerCore\LogArg.h" />
|
||||
<ClInclude Include="..\UNSWebServerCore\ServerLogger.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="源文件">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="头文件">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="资源文件">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CoreDeploy.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\UNSWebServerCore\LogArg.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\UNSWebServerCore\ServerLogger.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\UNSWebServerCore\Export.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\UNSWebServerCore\LogArg.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\UNSWebServerCore\ServerLogger.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,85 @@
|
||||
// ServerTest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include "ServerCore.h"
|
||||
#include "ServerLogger.h"
|
||||
#include "ServerProcessor.h"
|
||||
#include "TempFileManager.h"
|
||||
#include "SyncFileReceiver.h"
|
||||
#include "UNSResponseBuilder.h"
|
||||
|
||||
#pragma comment(lib, "UNSWebServerCore.lib")
|
||||
|
||||
class DemoProcessor : public ServerProcessor
|
||||
{
|
||||
public:
|
||||
uns::ResponsePtr Processor(uns::RequestPtr request) override
|
||||
{
|
||||
return uns::ResponseBuilder().OK().Body(request->GetDataS())();
|
||||
}
|
||||
};
|
||||
|
||||
class FileUploadDemo : public SyncFileReceiver
|
||||
{
|
||||
public:
|
||||
FileUploadDemo()
|
||||
{
|
||||
SyncFileReceiver::SetTempRoot("F:\\UNSWebServerCore\\cache");
|
||||
SyncFileReceiver::SetFileTimeout(30s, 60s);
|
||||
}
|
||||
|
||||
public:
|
||||
uns::Status PreCheckRequest(uns::RequestPtr request) override
|
||||
{
|
||||
SCLOGF_INFO("Upload Request, Size: {}", request->GetContentLength());
|
||||
return uns::kOK;
|
||||
}
|
||||
|
||||
bool PreCheckForm(uns::FormPartPtr form) override
|
||||
{
|
||||
SCLOGF_INFO("Form Size: {}", form->GetDataSize());
|
||||
return true;
|
||||
}
|
||||
|
||||
uns::ResponsePtr ProcessFiles(TempFileManager& file_info, SFR_FileMap file_map, const std::string& tmp_root, uns::RequestPtr request) override
|
||||
{
|
||||
SCLOGF_INFO("Current File Count: {}", file_map.size());
|
||||
for (const auto& [on, sn] : file_map)
|
||||
{
|
||||
SCLOGF_INFO("Current File: {} --> {}", on, sn);
|
||||
file_info.ActiveFile(sn); //Active File
|
||||
}
|
||||
SCLOGF_INFO("Global File Count: {}", file_info.GetFileCount());
|
||||
for (const auto& info : file_info.GetAllFileInfos())
|
||||
{
|
||||
SCLOGF_INFO("Global File [{}], Storaged AS [{}], Size: {}, Upload AT: {}", info.GetOriginalFileName(), info.GetStorageFileName(), info.GetFileSize(), info.GetUploadTime().Format("%Y-%m-%d %H:%M:%S"));
|
||||
}
|
||||
return uns::ResponseBuilder().OK()();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
SCLOG_CONSOLE_INIT(uns::llAll);
|
||||
|
||||
{
|
||||
ServerCore core(12345);
|
||||
|
||||
core.AppenedFileReceiver("/upload", std::make_shared<FileUploadDemo>(), uns::H_POST);
|
||||
core.AppenedProcessor(uns::url_all, std::make_shared<DemoProcessor>(), uns::H_ALL_ENABLED);
|
||||
|
||||
core.ThreadRun();
|
||||
|
||||
while (getchar() != '0')
|
||||
std::this_thread::sleep_for(0.1s);
|
||||
|
||||
core.Stop();
|
||||
}
|
||||
|
||||
SCLOG_CLOSE();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>18.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{462389a3-f5c9-4b03-87f8-83a7d421cb60}</ProjectGuid>
|
||||
<RootNamespace>ServerTest</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<IncludePath>F:\UNSWebServerCore\UNSWebServerCore;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>F:\UNSWebServerCore\x64\Release;$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<EnableSegmentHeap>true</EnableSegmentHeap>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<EnableSegmentHeap>true</EnableSegmentHeap>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<EnableSegmentHeap>true</EnableSegmentHeap>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<EnableSegmentHeap>true</EnableSegmentHeap>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ServerTest.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="源文件">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="头文件">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="资源文件">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ServerTest.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -3,6 +3,8 @@
|
||||
<Platform Name="x64" />
|
||||
<Platform Name="x86" />
|
||||
</Configurations>
|
||||
<Project Path="CoreDeploy/CoreDeploy.vcxproj" Id="71d27301-6fd9-456f-956c-a52ce5e12675" />
|
||||
<Project Path="ServerTest/ServerTest.vcxproj" Id="462389a3-f5c9-4b03-87f8-83a7d421cb60" />
|
||||
<Project Path="TestLogger/TestLogger.vcxproj" Id="bf89fe01-6bba-4c4c-b9b1-3daf5ef2b7f3" />
|
||||
<Project Path="UNSWebServerCore/UNSWebServerCore.vcxproj" Id="0c7bd295-ad5a-4643-a3e7-6bf6799c1467" />
|
||||
<Project Path="UOHash/UOHash.vcxproj" Id="696fff0a-4392-4ea6-8e41-1cb3867c4366" />
|
||||
|
||||
@@ -82,13 +82,13 @@ uns::ResponsePtr CORSProcessor::Processor(uns::RequestPtr request)
|
||||
std::string host = request->HasHeader("Host") ? request->GetHeader("Host") : "";
|
||||
if ((!host.empty()) && (!GlobalCORSConfig.HostValidate(host)))
|
||||
{
|
||||
SCLOG_WARNING("Rejected by Host check: %s", host.c_str());
|
||||
SCLOGF_WARNING("Rejected by Host check: {}", host);
|
||||
return uns::ResponseBuilder().Forbidden().EmptyBody()();
|
||||
}
|
||||
|
||||
if(!GlobalCORSConfig.UrlValidate(origin))
|
||||
{
|
||||
SCLOG_WARNING("Invalid CORS Origin: %s", origin.c_str());
|
||||
SCLOGF_WARNING("Invalid CORS Origin: {}", origin);
|
||||
return uns::ResponseBuilder().Forbidden().EmptyBody()();
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ uns::ResponsePtr CORSProcessor::Processor(uns::RequestPtr request)
|
||||
for(const auto& method : acrm_values)
|
||||
if(!GlobalCORSConfig.IsMethodAllowed(method))
|
||||
{
|
||||
SCLOG_WARNING("Invalid CORS Method: %s (All Methods: %d)", method.c_str(), acrm.c_str());
|
||||
SCLOGF_WARNING("Invalid CORS Method: {} (All Methods: {})", method, acrm);
|
||||
return uns::ResponseBuilder().NotAcceptable().EmptyBody()();
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ uns::ResponsePtr CORSProcessor::Processor(uns::RequestPtr request)
|
||||
auto valid_headers = GlobalCORSConfig.GetValidateHeaders(acrh_values);
|
||||
if((!acrh_values.empty()) && valid_headers.empty())
|
||||
{
|
||||
SCLOG_WARNING("Invalid CORS Header(s): %s", acrh.c_str());
|
||||
SCLOGF_WARNING("Invalid CORS Header(s): {}", acrh);
|
||||
return uns::ResponseBuilder().NotAcceptable().EmptyBody()();
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ uns::ResponsePtr CORSProcessor::Processor(uns::RequestPtr request)
|
||||
return uns::ResponseBuilder().Forbidden().EmptyBody()();
|
||||
}
|
||||
|
||||
SCLOG_INFO("CORS preflight allow origin=%s methods=%s headers=%s cred=%d", origin.c_str(), allow_methods_value.c_str(), valid_headers.c_str(), GlobalCORSConfig.AllowCookie() ? 1 : 0);
|
||||
SCLOGF_INFO("CORS preflight allow origin={} methods={} headers={} cred={}", origin, allow_methods_value, valid_headers, GlobalCORSConfig.AllowCookie() ? 1 : 0);
|
||||
return uns::ResponseBuilder().CORS_Full(origin, acrh_values).NoContent().EmptyBody()();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ DataTransfer::DataTransfer(const std::string& tr)
|
||||
void DataTransfer::Init(const std::string& tr)
|
||||
{
|
||||
temp_root = tr;
|
||||
SCLOG_DEBUG("GDT-TempRoot: %s", temp_root.c_str());
|
||||
SCLOGF_DEBUG("GDT-TempRoot: {}", temp_root);
|
||||
}
|
||||
|
||||
bool DataTransfer::ItemExist(const std::string& file)
|
||||
@@ -23,7 +23,7 @@ bool DataTransfer::InsertItem(const std::string& file)
|
||||
if (ItemExist(file))
|
||||
return false;
|
||||
files.insert({ file, true });
|
||||
SCLOG_INFO("GDT: Item [%s] Inserted", file.c_str());
|
||||
SCLOGF_INFO("GDT: Item [{}] Inserted", file);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ bool DataTransfer::RemoveItem(const std::string& file)
|
||||
if (!ItemExist(file))
|
||||
return false;
|
||||
files.erase(file);
|
||||
SCLOG_INFO("GDT: Item [%s] Removed", file.c_str());
|
||||
SCLOGF_INFO("GDT: Item [{}] Removed", file);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ void DataTransfer::DeactivateItem(const std::string& file)
|
||||
if (!ItemExist(file))
|
||||
return;
|
||||
files[file] = false;
|
||||
SCLOG_INFO("GDT: Item [%s] Deactivated", file.c_str());
|
||||
SCLOGF_INFO("GDT: Item [{}] Deactivated", file);
|
||||
}
|
||||
|
||||
bool DataTransfer::CopyItemTo(const std::string& file, const std::string& dest_path)
|
||||
@@ -59,19 +59,19 @@ bool DataTransfer::CopyItemTo(const std::string& file, const std::string& dest_p
|
||||
std::error_code error;
|
||||
if(!fs::exists(dest_path))
|
||||
{
|
||||
SCLOG_ERROR("GDT: Can't Copy File (Target Path [%s] Not Exist)", dest_path.c_str());
|
||||
SCLOGF_ERROR("GDT: Can't Copy File (Target Path [{}] Not Exist)", dest_path);
|
||||
return false;
|
||||
}
|
||||
fs::path dest(dest_path);
|
||||
fs::path dest_file = dest / file;
|
||||
if(fs::copy_file(MakePath(file), dest_file, fs::copy_options::overwrite_existing, error))
|
||||
{
|
||||
SCLOG_INFO("GDT: File Copied To [%s]", dest_file.c_str());
|
||||
SCLOGF_INFO("GDT: File Copied To [{}]", dest_file);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SCLOG_ERROR("Failed To Delete File [%s], Error: %s (%d)", dest_file.c_str(), error.message().c_str(), error.value());
|
||||
SCLOGF_ERROR("Failed To Delete File [{}], Error: {} ({})", dest_file, error.message(), error.value());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -85,12 +85,12 @@ bool DataTransfer::CopyItemAS(const std::string& file, const std::string& dest)
|
||||
fs::path dest_file = dest;
|
||||
if(fs::copy_file(MakePath(file), dest_file, fs::copy_options::overwrite_existing, error))
|
||||
{
|
||||
SCLOG_INFO("GDT: File Copied To [%s]", dest_file.c_str());
|
||||
SCLOGF_INFO("GDT: File Copied To [{}]", dest_file);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SCLOG_ERROR("Failed To Delete File [%s], Error: %s (%d)", dest_file.c_str(), error.message().c_str(), error.value());
|
||||
SCLOGF_ERROR("Failed To Delete File [{}], Error: {} ({})", dest_file, error.message(), error.value());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -111,14 +111,14 @@ bool DataTransfer::RemoveAllCacheFiles()
|
||||
std::string filepath = MakePath(file);
|
||||
if (!fs::exists(filepath, error))
|
||||
{
|
||||
SCLOG_WARNING("File [%s] Not Exist, Skip", filepath.c_str());
|
||||
SCLOGF_WARNING("File [{}] Not Exist, Skip", filepath);
|
||||
continue;
|
||||
}
|
||||
if (fs::remove(filepath, error))
|
||||
SCLOG_INFO("File [%s] Deleted", filepath.c_str());
|
||||
SCLOGF_INFO("File [{}] Deleted", filepath);
|
||||
else
|
||||
{
|
||||
SCLOG_ERROR("Failed To Delete File [%s], Error: %s (%d)", filepath.c_str(), error.message().c_str(), error.value());
|
||||
SCLOGF_ERROR("Failed To Delete File [{}], Error: {} ({})", filepath, error.message(), error.value());
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
@@ -137,14 +137,14 @@ fs_scan:
|
||||
if (fs::remove_all(entry.path(), error))
|
||||
{
|
||||
dirs_deleted++;
|
||||
SCLOG_WARNING("Found Directory [%s] In Cache Directory, Deleted", entry.path().string().c_str());
|
||||
SCLOGF_WARNING("Found Directory [{}] In Cache Directory, Deleted", entry.path().string());
|
||||
}
|
||||
else
|
||||
SCLOG_ERROR("Found Directory [%s] In Cache Directory, Failed To Delete. Error: %s (%d)", entry.path().string().c_str(), error.message().c_str(), error.value());
|
||||
SCLOGF_ERROR("Found Directory [{}] In Cache Directory, Failed To Delete. Error: {} ({})", entry.path().string(), error.message(), error.value());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
SCLOG_ERROR("Found Directory [%s] In Cache Directory, Failed To Delete. Exception: %s", entry.path().string().c_str(), e.what());
|
||||
SCLOGF_ERROR("Found Directory [{}] In Cache Directory, Failed To Delete. Exception: {}", entry.path().string(), e.what());
|
||||
}
|
||||
}
|
||||
else if (entry.is_regular_file())
|
||||
@@ -153,16 +153,16 @@ fs_scan:
|
||||
if (fs::remove(entry.path(), error))
|
||||
{
|
||||
files_deleted++;
|
||||
SCLOG_INFO("File [%s] Deleted", entry.path().string().c_str());
|
||||
SCLOGF_INFO("File [{}] Deleted", entry.path().string());
|
||||
}
|
||||
else
|
||||
{
|
||||
SCLOG_ERROR("Failed To Delete File [%s], Error: %s (%d)", entry.path().string().c_str(), error.message().c_str(), error.value());
|
||||
SCLOGF_ERROR("Failed To Delete File [{}], Error: {} ({})", entry.path().string(), error.message(), error.value());
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
SCLOG_INFO("Filesystem Scan Finished, %d/%d Dir(s) And %d/%d Files(s) Deleted", dirs_deleted, dirs, files_deleted, files);
|
||||
SCLOGF_INFO("Filesystem Scan Finished, {}/{} Dir(s) And {}/{} Files(s) Deleted", dirs_deleted, dirs, files_deleted, files);
|
||||
SCLOG_INFO("Cache Clear Finished");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@ public:
|
||||
bool HTMLResponse = false;
|
||||
std::string TempRoot;
|
||||
IPTablePtr BlockedIPs = nullptr;
|
||||
WebFileInfoVec FileInfo;
|
||||
TempFileManager FileManager;
|
||||
FileProcessorCallback Callback = nullptr;
|
||||
std::jthread FileProcesser;
|
||||
std::chrono::seconds FileTimeout = 300s, FileMaxProcTimeout = 600s;
|
||||
};
|
||||
|
||||
FileReceiver::FileReceiver() : pimpl(std::make_unique<Impl>())
|
||||
@@ -27,11 +29,11 @@ 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())
|
||||
pimpl->FileProcesser = std::jthread(pimpl->Callback, std::ref(pimpl->FileManager), pimpl->TempRoot);
|
||||
if (!pimpl->FileProcesser.joinable())
|
||||
return false;
|
||||
thFileProcesser.detach();
|
||||
pimpl->FileInfo.clear();
|
||||
pimpl->FileProcesser.detach();
|
||||
//pimpl->FileInfo.clear();
|
||||
SCLOGF_TRACE("FileProcesser Function (Address: {}) Started.", pimpl->Callback);
|
||||
return true;
|
||||
}
|
||||
@@ -39,19 +41,19 @@ bool FileReceiver::CallFileProcesser()
|
||||
void FileReceiver::SetResponseMode(bool html)
|
||||
{
|
||||
pimpl->HTMLResponse = html;
|
||||
SCLOG_DEBUG("FileReceiver init mode: %s", (html ? "html" : "json"));
|
||||
SCLOGF_DEBUG("FileReceiver init mode: {}", (html ? "html" : "json"));
|
||||
}
|
||||
|
||||
void FileReceiver::SetCORSEnable(bool enable)
|
||||
{
|
||||
pimpl->EnableCORS = enable;
|
||||
SCLOG_DEBUG("FileReceiver CORS mode: %s", (enable ? "enabled" : "disabled"));
|
||||
SCLOGF_DEBUG("FileReceiver CORS mode: {}", (enable ? "enabled" : "disabled"));
|
||||
}
|
||||
|
||||
void FileReceiver::SetTempRoot(std::string temp_root)
|
||||
{
|
||||
pimpl->TempRoot = temp_root;
|
||||
SCLOG_TRACE("FR-TempRoot: %s", pimpl->TempRoot.c_str());
|
||||
SCLOGF_TRACE("FR-TempRoot: {}", pimpl->TempRoot);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,7 +75,7 @@ void FileReceiver::AppenedBlockedIP(DateTime::Span block_time, std::string ip)
|
||||
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());
|
||||
SCLOGF_INFO("IP: [{}] has been blocked untill {{{}}}", ip, std::string(expr_time));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -108,13 +110,24 @@ bool FileReceiver::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());
|
||||
SCLOGF_WARNING("Failed to write file [{}]: can't open stream", path);
|
||||
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();
|
||||
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 FileReceiver::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 FileReceiver::PTDefence()
|
||||
@@ -128,20 +141,36 @@ bool FileReceiver::IsPathSafe(const std::string& raw_path)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileReceiver::IsHeaderValid(uns::RequestPtr request)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
try
|
||||
{
|
||||
Json::Value sub;
|
||||
sub["FileName"] = ele.GetStorageFileName();
|
||||
sub["UploadTime"] = ele.GetUploadTime().GetTimeStamp();
|
||||
root["AcceptedFiles"].append(sub);
|
||||
Json::Value root;
|
||||
Json::FastWriter writer;
|
||||
// 1. 从管理器安全获取当前所有文件的快照
|
||||
auto file_infos = pimpl->FileManager.GetAllFileInfos();
|
||||
// 2. 组装 JSON 数据
|
||||
root["AcceptedCount"] = static_cast<Json::Value::UInt64>(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\":[]}";
|
||||
}
|
||||
return writer.write(root);
|
||||
}
|
||||
|
||||
std::string FileReceiver::EncodeUploadResultHTML()
|
||||
@@ -162,16 +191,32 @@ std::string FileReceiver::EncodeUploadResultHTML()
|
||||
</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;
|
||||
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") + "}<br>";
|
||||
// 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<long long>(file_infos.size()), tmp.c_str());
|
||||
if (written > 0)
|
||||
{
|
||||
result_str.resize(written); // 裁剪掉尾部多余的 \0
|
||||
return result_str;
|
||||
}
|
||||
return "HTML generation failed";
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// 异常安全兜底
|
||||
return "<html><body><center><h1>Upload Result Error</h1></center></body></html>";
|
||||
}
|
||||
}
|
||||
|
||||
uns::ResponsePtr FileReceiver::Execute(uns::RequestPtr request)
|
||||
@@ -181,7 +226,7 @@ uns::ResponsePtr FileReceiver::Execute(uns::RequestPtr request)
|
||||
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());
|
||||
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);
|
||||
@@ -245,7 +290,9 @@ uns::ResponsePtr FileReceiver::Execute(uns::RequestPtr request)
|
||||
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);
|
||||
//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);
|
||||
}
|
||||
std::string resp_body = (pimpl->HTMLResponse ? EncodeUploadResultHTML() : EncodeUploadResult());
|
||||
CallFileProcesser();
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
#include "Global.h"
|
||||
#include "IPTable.h"
|
||||
#include <functional>
|
||||
#include "WebFileInfo.h"
|
||||
#include "HTTPObjects.h"
|
||||
#include "TempFileManager.h"
|
||||
|
||||
using FileProcessorCallback = std::function<void(WebFileInfoVec, const std::string&)>;
|
||||
using FileProcessorCallback = std::function<void(TempFileManager&, const std::string&)>;
|
||||
|
||||
class UNSWSC_DLL_EXPORT FileReceiver
|
||||
{
|
||||
@@ -29,12 +29,19 @@ public:
|
||||
uns::HTTPMethod GetMethod(uns::RequestPtr request);
|
||||
void AppenedBlockedIP(DateTime::Span block_time, std::string ip);
|
||||
bool WriteFile(const std::string& path, const std::string& bytes);
|
||||
void SetFileTimeout(std::chrono::seconds timeout = 0s, std::chrono::seconds max_proc_timeout = 0s) noexcept;
|
||||
|
||||
public:
|
||||
// 请求信息预检,重载以在保存文件之前检查请求体
|
||||
virtual uns::Status PreCheckRequest(uns::RequestPtr request) = 0;
|
||||
// 表单预检,重载以在处理表单前检查表单
|
||||
virtual bool PreCheckForm(uns::FormPartPtr form) = 0;
|
||||
// 路径穿越配置,重载以配置允许的路径穿越类型
|
||||
virtual uns::PathTraversalDefenceLevel PTDefence();
|
||||
// 路径穿越防护,重载以实现路径穿越检查,配置为AutoNormalize或AllowNormal时必须,否则自动退化为DenyAll
|
||||
virtual bool IsPathSafe(const std::string& raw_path);
|
||||
// 请求头预检,重载以实现在接收请求体之前检查请求头,返回false则服务器将强制关闭连接
|
||||
virtual bool IsHeaderValid(uns::RequestPtr request);
|
||||
|
||||
public:
|
||||
// 核心驱动入口:供内部适配器调用的实际执行流
|
||||
|
||||
@@ -167,7 +167,7 @@ std::string uns::tools::ToLower(const std::string& s)
|
||||
return r;
|
||||
}
|
||||
|
||||
std::string uns::tools::CalculateFileHashSHA256(const std::string & file)
|
||||
std::string uns::tools::CalculateFileHashSHA256(const std::string& file)
|
||||
{
|
||||
// 1. 以二进制模式打开文件
|
||||
std::ifstream ifs(file, std::ios::binary);
|
||||
@@ -175,7 +175,7 @@ std::string uns::tools::CalculateFileHashSHA256(const std::string & file)
|
||||
return std::string();
|
||||
// 2. 初始化 OpenSSL EVP 上下文,使用智能指针自动管理内存释放
|
||||
std::unique_ptr<EVP_MD_CTX, void(*)(EVP_MD_CTX*)> ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
||||
if (!ctx)
|
||||
if (!ctx)
|
||||
return std::string();
|
||||
// 3. 指定使用 SHA256 算法
|
||||
if (EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr) != 1)
|
||||
@@ -205,7 +205,100 @@ std::string uns::tools::CalculateFileHashSHA256(const std::string & file)
|
||||
return hex_result;
|
||||
}
|
||||
|
||||
bool uns::secure::IsSafePath(const std::string & safe_path, const std::string & requested_path)
|
||||
Json::Value uns::tools::SafeJsonDecode(const std::string& str)
|
||||
{
|
||||
try
|
||||
{
|
||||
Json::Value root;
|
||||
Json::Reader reader;
|
||||
if (!reader.parse(str, root, false))
|
||||
return Json::nullValue;
|
||||
else
|
||||
return root;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return Json::nullValue;
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename> inline constexpr bool always_false_v = false;
|
||||
|
||||
template <typename T, typename CharT>
|
||||
T do_sto(const std::basic_string<CharT>& str, std::size_t* pos, int base)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, int>)
|
||||
return std::stoi(str, pos, base);
|
||||
else if constexpr (std::is_same_v<T, long>)
|
||||
return std::stol(str, pos, base);
|
||||
else if constexpr (std::is_same_v<T, long long>)
|
||||
return std::stoll(str, pos, base);
|
||||
else if constexpr (std::is_same_v<T, unsigned long>)
|
||||
return std::stoul(str, pos, base);
|
||||
else if constexpr (std::is_same_v<T, unsigned long long>)
|
||||
return std::stoull(str, pos, base);
|
||||
else if constexpr (std::is_same_v<T, float>)
|
||||
return std::stof(str, pos);
|
||||
else if constexpr (std::is_same_v<T, double>)
|
||||
return std::stod(str, pos);
|
||||
else if constexpr (std::is_same_v<T, long double>)
|
||||
return std::stold(str, pos);
|
||||
else
|
||||
static_assert(always_false_v<T>, "不支持的转换类型!");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename CharT>
|
||||
std::optional<T> uns::tools::SafeStoX(const std::basic_string<CharT>& str, std::size_t* pos, int base)
|
||||
{
|
||||
try
|
||||
{
|
||||
return do_sto<T>(str, pos, base);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename CharT>
|
||||
std::optional<T> uns::tools::SafeStoX(std::basic_string_view<CharT> str, std::size_t* pos, int base)
|
||||
{
|
||||
return SafeStoX<T>(std::basic_string<CharT>(str), pos, base);
|
||||
}
|
||||
|
||||
bool uns::secure::IsSafePath(const std::string& safe_path, const std::string& requested_path)
|
||||
{
|
||||
return PathTraversal::IsSafePath(safe_path, requested_path);
|
||||
}
|
||||
|
||||
namespace uns
|
||||
{
|
||||
namespace tools
|
||||
{
|
||||
// -------------------------------------------------------------
|
||||
// 显式模板实例化(注意:必须放在命名空间内部!)
|
||||
// -------------------------------------------------------------
|
||||
#define INSTANTIATE_SAFE_STO(T, CharT) \
|
||||
template std::optional<T> UNSWSC_DLL_EXPORT SafeStoX<T, CharT>(const std::basic_string<CharT>&, std::size_t*, int); \
|
||||
template std::optional<T> UNSWSC_DLL_EXPORT SafeStoX<T, CharT>(std::basic_string_view<CharT>, std::size_t*, int);
|
||||
|
||||
#define INSTANTIATE_ALL_NUMERIC_TYPES(CharT) \
|
||||
INSTANTIATE_SAFE_STO(int, CharT) \
|
||||
INSTANTIATE_SAFE_STO(long, CharT) \
|
||||
INSTANTIATE_SAFE_STO(long long, CharT) \
|
||||
INSTANTIATE_SAFE_STO(unsigned long, CharT) \
|
||||
INSTANTIATE_SAFE_STO(unsigned long long, CharT) \
|
||||
INSTANTIATE_SAFE_STO(float, CharT) \
|
||||
INSTANTIATE_SAFE_STO(double, CharT) \
|
||||
INSTANTIATE_SAFE_STO(long double, CharT)
|
||||
|
||||
INSTANTIATE_ALL_NUMERIC_TYPES(char)
|
||||
INSTANTIATE_ALL_NUMERIC_TYPES(wchar_t)
|
||||
|
||||
#undef INSTANTIATE_ALL_NUMERIC_TYPES
|
||||
#undef INSTANTIATE_SAFE_STO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include "Export.h"
|
||||
#include "DateTime.h"
|
||||
|
||||
@@ -35,6 +36,11 @@ constexpr auto G_ERROR_PAGE = R"(
|
||||
|
||||
// inline constexpr std::string_view G_HTTP_STD_WEEK[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
||||
|
||||
namespace Json
|
||||
{
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace uns
|
||||
{
|
||||
enum HTTPMethod
|
||||
@@ -65,6 +71,8 @@ namespace uns
|
||||
inline constexpr std::string_view resh_acma = "Access-Control-Max-Age";
|
||||
};
|
||||
|
||||
inline constexpr auto url_all = R"(/[\s\S]*)";
|
||||
|
||||
using POSTArgs = std::map<std::string, std::string>;
|
||||
|
||||
std::string UNSWSC_DLL_EXPORT EncodeErrorPage(int code);
|
||||
@@ -93,6 +101,23 @@ namespace uns
|
||||
std::string UNSWSC_DLL_EXPORT ToLower(const std::string& s);
|
||||
|
||||
std::string UNSWSC_DLL_EXPORT CalculateFileHashSHA256(const std::string& file);
|
||||
|
||||
Json::Value UNSWSC_DLL_EXPORT SafeJsonDecode(const std::string& str);
|
||||
|
||||
template <typename T, typename CharT = char>
|
||||
std::optional<T> UNSWSC_DLL_EXPORT SafeStoX(const std::basic_string<CharT>& str, std::size_t* pos = nullptr, int base = 10);
|
||||
template <typename T, typename CharT = char>
|
||||
std::optional<T> UNSWSC_DLL_EXPORT SafeStoX(std::basic_string_view<CharT> str, std::size_t* pos = nullptr, int base = 10);
|
||||
template <typename T, typename CharT>
|
||||
inline std::optional<T> SafeStoX(const CharT* str, std::size_t* pos = nullptr, int base = 10)
|
||||
{
|
||||
#if defined(__cpp_char8_t)
|
||||
if constexpr (std::is_same_v<CharT, char8_t>)
|
||||
return SafeStoX<T>(std::basic_string<char>(reinterpret_cast<const char*>(str)), pos, base); // 只有 u8"..." (char8_t) 强制转为 char 版本的 SafeStoX 处理
|
||||
else
|
||||
#endif
|
||||
return SafeStoX<T>(std::basic_string<CharT>(str), pos, base); // 普通 "..." (char) 和 L"..." (wchar_t) 保持各自类型,构造对应的 basic_string
|
||||
}
|
||||
}
|
||||
|
||||
namespace secure
|
||||
@@ -103,7 +128,7 @@ namespace uns
|
||||
* @param requested_path 客户端传入的、解码后的目标子路径
|
||||
* @return true 安全(在沙盒内);false 不安全(企图穿越或路径非法)
|
||||
*/
|
||||
bool IsSafePath(const std::string& safe_path, const std::string& requested_path);
|
||||
bool IsSafePath(const std::string& safe_path, const std::string& requested_path);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <variant>
|
||||
#include "Export.h"
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -217,6 +218,8 @@ namespace uns
|
||||
else if constexpr (std::is_pointer_v<D>)
|
||||
value = static_cast<const void*>(val);
|
||||
// 7. 标准库容器(关键点:利用 Lambda 闭包在不引入 fmt 的情况下擦除容器类型!)
|
||||
else if constexpr (std::is_same<D, std::filesystem::path>::value)
|
||||
value = ConvertWStringToUtf8(val.generic_wstring());
|
||||
else if constexpr (is_container<D>::value)
|
||||
{
|
||||
value = RangeCapturer{ &val, [] (const void* p, std::vector<LogArg>& out)
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace uns
|
||||
}
|
||||
|
||||
// 完美的把 webcc 的驱动流,翻译给用户的纯净业务类
|
||||
webcc::ResponsePtr Handle(webcc::RequestPtr request) final
|
||||
webcc::ResponsePtr Handle(webcc::RequestPtr request) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -53,7 +53,7 @@ namespace uns
|
||||
return uns::ResponseBuilder().InternalServerError()()->GetImpl()->webcc_res; //Default 500
|
||||
}
|
||||
|
||||
bool Stream(const std::string& method) final
|
||||
bool Stream(const std::string& method) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -70,7 +70,25 @@ namespace uns
|
||||
return false; //Default false
|
||||
}
|
||||
|
||||
void ApplyIpUpdate(IPTablePtr blocked_ips) override
|
||||
bool ValidateHeader(webcc::RequestPtr request) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
auto uns_req = uns::RequestPtr(new uns::Request(std::make_unique<uns::Request::Impl>(request)));
|
||||
return user_processor->IsHeaderValid(uns_req);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
SCLOGF_ERROR("Unhandled Exception in ServerProcessorAdapter(ValidateHeader): {}", e.what());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
SCLOGF_FATAL("Unhandled Unknown Exception in ServerProcessorAdapter(ValidateHeader)");
|
||||
}
|
||||
return true; //Default true
|
||||
}
|
||||
|
||||
void ApplyIpUpdate(IPTablePtr blocked_ips) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -98,7 +116,7 @@ namespace uns
|
||||
}
|
||||
|
||||
// 完美的把 webcc 的驱动流,翻译给用户的纯净业务类
|
||||
webcc::ResponsePtr Handle(webcc::RequestPtr request) final
|
||||
webcc::ResponsePtr Handle(webcc::RequestPtr request) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -118,13 +136,31 @@ namespace uns
|
||||
return uns::ResponseBuilder().InternalServerError()()->GetImpl()->webcc_res; //Default 500
|
||||
}
|
||||
|
||||
bool Stream(const std::string& method) final
|
||||
bool Stream(const std::string& method) override final
|
||||
{
|
||||
// 所有数据都不能由webcc进行串流,否则将无法从request中获取文件
|
||||
return false;
|
||||
}
|
||||
|
||||
void ApplyIpUpdate(IPTablePtr blocked_ips) override
|
||||
bool ValidateHeader(webcc::RequestPtr request) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
auto uns_req = uns::RequestPtr(new uns::Request(std::make_unique<uns::Request::Impl>(request)));
|
||||
return user_reciver->IsHeaderValid(uns_req);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
SCLOGF_ERROR("Unhandled Exception in FileReceiverAdapter(ValidateHeader): {}", e.what());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
SCLOGF_FATAL("Unhandled Unknown Exception in FileReceiverAdapter(ValidateHeader)");
|
||||
}
|
||||
return true; //Default true
|
||||
}
|
||||
|
||||
void ApplyIpUpdate(IPTablePtr blocked_ips) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -152,7 +188,7 @@ namespace uns
|
||||
}
|
||||
|
||||
// 完美的把 webcc 的驱动流,翻译给用户的纯净业务类
|
||||
webcc::ResponsePtr Handle(webcc::RequestPtr request) final
|
||||
webcc::ResponsePtr Handle(webcc::RequestPtr request) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -172,13 +208,31 @@ namespace uns
|
||||
return uns::ResponseBuilder().InternalServerError()()->GetImpl()->webcc_res; //Default 500
|
||||
}
|
||||
|
||||
bool Stream(const std::string& method) final
|
||||
bool Stream(const std::string& method) override final
|
||||
{
|
||||
// 所有数据都不能由webcc进行串流,否则将无法从request中获取文件
|
||||
return false;
|
||||
}
|
||||
|
||||
void ApplyIpUpdate(IPTablePtr blocked_ips) override
|
||||
bool ValidateHeader(webcc::RequestPtr request) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
auto uns_req = uns::RequestPtr(new uns::Request(std::make_unique<uns::Request::Impl>(request)));
|
||||
return user_reciver->IsHeaderValid(uns_req);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
SCLOGF_ERROR("Unhandled Exception in SyncFileReceiverAdapter(ValidateHeader): {}", e.what());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
SCLOGF_FATAL("Unhandled Unknown Exception in SyncFileReceiverAdapter(ValidateHeader)");
|
||||
}
|
||||
return true; //Default true
|
||||
}
|
||||
|
||||
void ApplyIpUpdate(IPTablePtr blocked_ips) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
{
|
||||
if (server == nullptr)
|
||||
return;
|
||||
server->set_buffer_size(10240);
|
||||
server->set_buffer_size(65535);
|
||||
SCLOGF_INFO("ServerCore thread ready: {} Worker(s), {} Loop(s)", worker_thread, loop_thread);
|
||||
server->Run(worker_thread, loop_thread);
|
||||
return;
|
||||
@@ -142,9 +142,9 @@ bool ServerCore::AppenedProcessor(std::string url, ServerProcessorPtr ptr, std::
|
||||
auto adapter = std::make_shared<uns::ServerProcessorAdapter>(ptr);
|
||||
bool bret = pimpl->ccServer->Route(webcc::UrlRegex(url), adapter, Impl::ConvertMethods(methods));
|
||||
if (bret)
|
||||
SCLOG_INFO("ServerProcessor added. URL: [%s], Method code: <%s>", url.c_str(), uns::toBinary(methods, 10).c_str());
|
||||
SCLOGF_INFO("ServerProcessor added. URL: [{}], Method code: <{}>", url, uns::toBinary(methods, 10));
|
||||
else
|
||||
SCLOG_ERROR("ServerProcessor add faliure. URL: [%s], Method code: <%s>", url.c_str(), uns::toBinary(methods, 10).c_str());
|
||||
SCLOGF_ERROR("ServerProcessor add faliure. URL: [{}], Method code: <{}>", url, uns::toBinary(methods, 10));
|
||||
return bret;
|
||||
}
|
||||
|
||||
@@ -156,9 +156,9 @@ bool ServerCore::AppenedFileReceiver(std::string url, FileReceiverPtr ptr, std::
|
||||
auto adapter = std::make_shared<uns::FileReceiverAdapter>(ptr);
|
||||
bool bret = pimpl->ccServer->Route(webcc::UrlRegex(url), adapter, Impl::ConvertMethods(methods));
|
||||
if (bret)
|
||||
SCLOG_INFO("FileReceiver added. URL: [%s], Method code: <%s>", url.c_str(), uns::toBinary(methods, 10).c_str());
|
||||
SCLOGF_INFO("FileReceiver added. URL: [{}], Method code: <{}>", url, uns::toBinary(methods, 10));
|
||||
else
|
||||
SCLOG_ERROR("FileReceiver add faliure. URL: [%s], Method code: <%s>", url.c_str(), uns::toBinary(methods, 10).c_str());
|
||||
SCLOGF_ERROR("FileReceiver add faliure. URL: [{}], Method code: <{}>", url, uns::toBinary(methods, 10));
|
||||
return bret;
|
||||
}
|
||||
|
||||
@@ -169,9 +169,9 @@ bool ServerCore::AppenedFileReceiver(std::string url, SyncFileReceiverPtr ptr, s
|
||||
auto adapter = std::make_shared<uns::SyncFileReceiverAdapter>(ptr);
|
||||
bool bret = pimpl->ccServer->Route(webcc::UrlRegex(url), adapter, Impl::ConvertMethods(methods));
|
||||
if (bret)
|
||||
SCLOG_INFO("SyncFileReceiver added. URL: [%s], Method code: <%s>", url.c_str(), uns::toBinary(methods, 10).c_str());
|
||||
SCLOGF_INFO("SyncFileReceiver added. URL: [{}], Method code: <{}>", url, uns::toBinary(methods, 10));
|
||||
else
|
||||
SCLOG_ERROR("SyncFileReceiver add faliure. URL: [%s], Method code: <%s>", url.c_str(), uns::toBinary(methods, 10).c_str());
|
||||
SCLOGF_ERROR("SyncFileReceiver add faliure. URL: [{}], Method code: <{}>", url, uns::toBinary(methods, 10));
|
||||
return bret;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Unknown Network Service Web Server Core
|
||||
* Version 1.2.2
|
||||
*
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <fmt/args.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <array>
|
||||
|
||||
// 格式化用的辅助函数
|
||||
|
||||
@@ -168,14 +169,174 @@ std::string uns::toBinary(unsigned long number, int bits)
|
||||
return "0b" + res;
|
||||
}
|
||||
|
||||
bool StartsWith(std::string_view str, std::string_view prefix)
|
||||
{
|
||||
if (str.size() < prefix.size())
|
||||
return false;
|
||||
return str.compare(0, prefix.size(), prefix) == 0;
|
||||
}
|
||||
|
||||
bool IsSizeUnit(std::string_view str)
|
||||
{
|
||||
if (str == "bit")
|
||||
return true;
|
||||
if (str.empty())
|
||||
return false;
|
||||
char last = str.back();
|
||||
return last == 'b' || last == 'B';
|
||||
}
|
||||
|
||||
bool IsNonNegativeInteger(std::string_view str, int& value)
|
||||
{
|
||||
if (str.empty())
|
||||
return false;
|
||||
int result = 0;
|
||||
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
|
||||
if (ec != std::errc() || ptr != str.data() + str.size())
|
||||
return false;
|
||||
if (result < 0)
|
||||
return false;
|
||||
value = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<std::string, int> ParseSizeFormat(std::string_view input)
|
||||
{
|
||||
constexpr std::pair<std::string_view, int> default_value = { "B", 2 };
|
||||
if (!StartsWith(input, "siz"))
|
||||
return { std::string(default_value.first), default_value.second };
|
||||
|
||||
std::string_view body = input.substr(3);
|
||||
if (body.empty() || body.front() != '-')
|
||||
return { std::string(default_value.first), default_value.second };
|
||||
|
||||
body.remove_prefix(1);
|
||||
size_t first_dash = body.find('-');
|
||||
if (first_dash == std::string_view::npos)
|
||||
{
|
||||
// siz-xx 或 siz-x
|
||||
if (IsSizeUnit(body))
|
||||
return { std::string(body), 2 };
|
||||
int precision = 0;
|
||||
if (IsNonNegativeInteger(body, precision))
|
||||
return { "B", precision };
|
||||
return { std::string(default_value.first), default_value.second };
|
||||
}
|
||||
|
||||
// siz-xx-y
|
||||
std::string_view unit = body.substr(0, first_dash);
|
||||
std::string_view precision_str = body.substr(first_dash + 1);
|
||||
if (!IsSizeUnit(unit))
|
||||
return { std::string(default_value.first), default_value.second };
|
||||
int precision = 0;
|
||||
if (!IsNonNegativeInteger(precision_str, precision))
|
||||
return { std::string(default_value.first), default_value.second };
|
||||
return { std::string(unit), precision };
|
||||
}
|
||||
|
||||
std::string FormatFileSize(size_t size, const std::string& unit, int precision)
|
||||
{
|
||||
static constexpr double K = 1024.0;
|
||||
static constexpr std::array<const char*, 9> units =
|
||||
{
|
||||
"B",
|
||||
"KB",
|
||||
"MB",
|
||||
"GB",
|
||||
"TB",
|
||||
"PB",
|
||||
"EB",
|
||||
"ZB",
|
||||
"YB"
|
||||
};
|
||||
|
||||
static constexpr std::array<const char*, 9> iec_units =
|
||||
{
|
||||
"B",
|
||||
"KIB",
|
||||
"MIB",
|
||||
"GIB",
|
||||
"TIB",
|
||||
"PIB",
|
||||
"EIB",
|
||||
"ZIB",
|
||||
"YIB"
|
||||
};
|
||||
|
||||
std::string input_unit = unit;
|
||||
std::transform(input_unit.begin(), input_unit.end(), input_unit.begin(), [] (unsigned char c)
|
||||
{
|
||||
return static_cast<char>(std::toupper(c));
|
||||
});
|
||||
|
||||
double bytes = static_cast<double>(size);
|
||||
if ((input_unit == "BIT") || (input_unit == "BITS"))
|
||||
bytes /= 8.0;
|
||||
else
|
||||
{
|
||||
size_t unit_index = 0;
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < units.size(); ++i)
|
||||
{
|
||||
if ((input_unit == units[i]) || (input_unit == iec_units[i]))
|
||||
{
|
||||
unit_index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return std::format("{} {}", size, input_unit);
|
||||
for (size_t i = 0; i < unit_index; ++i)
|
||||
bytes *= K;
|
||||
}
|
||||
|
||||
size_t output_index = 0;
|
||||
while ((bytes >= K) && (output_index < (units.size() - 1)))
|
||||
{
|
||||
bytes /= K;
|
||||
++output_index;
|
||||
}
|
||||
if (precision < 0)
|
||||
precision = 0;
|
||||
|
||||
if (std::fabs(bytes - std::round(bytes)) < std::numeric_limits<double>::epsilon())
|
||||
return fmt::format("{} {}", static_cast<size_t>(std::round(bytes)), units[output_index]);
|
||||
|
||||
std::string value = fmt::format("{:.{}f}", bytes, precision);
|
||||
return fmt::format("{} {}", value, units[output_index]);
|
||||
}
|
||||
|
||||
std::string ServerLogger::GenerateLogHeader(uns::ServerLogLevel LogLevel)
|
||||
{
|
||||
std::string hstr;
|
||||
// 在日志行最开头添加对应日志级别的颜色控制码
|
||||
switch (LogLevel)
|
||||
{
|
||||
case uns::llDebug:
|
||||
hstr += "\033[36m"; // 青色
|
||||
break;
|
||||
case uns::llInfo:
|
||||
hstr += "\033[32m"; // 绿色
|
||||
break;
|
||||
case uns::llWarning:
|
||||
hstr += "\033[33m"; // 黄色
|
||||
break;
|
||||
case uns::llError:
|
||||
hstr += "\033[31m"; // 红色
|
||||
break;
|
||||
case uns::llFatal:
|
||||
hstr += "\033[1;37;41m"; // 亮白字 + 红底 (极度醒目)
|
||||
break;
|
||||
case uns::llTrace:
|
||||
default:
|
||||
break; // TRACE 与未知级别保持默认颜色,不追加转义码
|
||||
}
|
||||
time_t lt = time(NULL);
|
||||
tm* loctim = localtime(<);
|
||||
char timestr[250] = {};
|
||||
sprintf(timestr, "{%04d-%02d-%02d %02d:%02d:%02d} ", loctim->tm_year + 1900, loctim->tm_mon + 1, loctim->tm_mday, loctim->tm_hour, loctim->tm_min, loctim->tm_sec);
|
||||
hstr = timestr;
|
||||
hstr += timestr;
|
||||
switch (LogLevel)
|
||||
{
|
||||
case uns::llTrace:
|
||||
@@ -211,6 +372,40 @@ std::string ServerLogger::GenerateFileInfo(std::string filename, int line_num)
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// 高性能、100% 异常安全的 ANSI 颜色码剥离函数
|
||||
inline std::string StripAnsiCodes(const std::string& input) noexcept
|
||||
{
|
||||
std::string result;
|
||||
// 预分配内存,避免多次 Realloc(即使底层内存极度匮乏,noexcept 也会兜底)
|
||||
try
|
||||
{
|
||||
result.reserve(input.size());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// 极罕见的内存耗尽情况,直接降级返回原串或空串,绝不崩溃
|
||||
return input;
|
||||
}
|
||||
bool in_escape = false;
|
||||
for (char c : input)
|
||||
{
|
||||
if (c == '\033') // 遇到转义字符 '\033' (ESC)
|
||||
{
|
||||
in_escape = true;
|
||||
continue;
|
||||
}
|
||||
if (in_escape)
|
||||
{
|
||||
// ANSI 颜色控制码以 'm' 结尾(例如 \033[31m 或 \033[0m)
|
||||
if (c == 'm')
|
||||
in_escape = false;
|
||||
continue; // 跳过转义序列内的所有字符
|
||||
}
|
||||
result.push_back(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ServerLogger::WriteBatchToOutputs(const std::deque<std::string>& batch)
|
||||
{
|
||||
if (batch.empty())
|
||||
@@ -222,7 +417,7 @@ void ServerLogger::WriteBatchToOutputs(const std::deque<std::string>& batch)
|
||||
continue;
|
||||
std::fwrite(item.c_str(), 1, item.size(), stdout);
|
||||
if (LogStream.is_open())
|
||||
LogStream << item;
|
||||
LogStream << StripAnsiCodes(item);
|
||||
RotateIfNeeded(now, true);
|
||||
}
|
||||
std::fflush(stdout);
|
||||
@@ -355,6 +550,18 @@ void ServerLogger::RotateIfNeeded(std::time_t now, bool check_size_after_write)
|
||||
}
|
||||
}
|
||||
|
||||
size_t GetUnsignedInteger(const uns::LogVariant& value)
|
||||
{
|
||||
return std::visit([] (const auto& v) -> size_t
|
||||
{
|
||||
using T = std::decay_t<decltype(v)>;
|
||||
if constexpr (std::is_integral_v<T> && !std::is_same_v<T, char> && !std::is_same_v<T, bool>)
|
||||
return static_cast<size_t>(v);
|
||||
else
|
||||
return 0;
|
||||
}, value);
|
||||
}
|
||||
|
||||
inline std::string RewriteFormatString(const std::string& real_format, const uns::LogArg* args, size_t count, fmt::dynamic_format_arg_store<fmt::format_context>& store)
|
||||
{
|
||||
std::string out;
|
||||
@@ -366,14 +573,14 @@ inline std::string RewriteFormatString(const std::string& real_format, const uns
|
||||
// escaped {{
|
||||
if ((c == '{') && ((i + 1) < real_format.size()) && (real_format[i + 1] == '{'))
|
||||
{
|
||||
out += '{';
|
||||
out += "{{";
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
// escaped }}
|
||||
if ((c == '}') && ((i + 1) < real_format.size()) && (real_format[i + 1] == '}'))
|
||||
{
|
||||
out += '}';
|
||||
out += "}}";
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
@@ -407,6 +614,11 @@ inline std::string RewriteFormatString(const std::string& real_format, const uns
|
||||
else
|
||||
store.push_back(args[arg_index]);
|
||||
}
|
||||
else if (StartsWith(inside, "siz"))
|
||||
{
|
||||
auto [u, p] = ParseSizeFormat(inside);
|
||||
store.push_back(FormatFileSize(GetUnsignedInteger(arg.value), u, p));
|
||||
}
|
||||
else
|
||||
store.push_back(args[arg_index]);
|
||||
}
|
||||
@@ -421,6 +633,9 @@ inline std::string RewriteFormatString(const std::string& real_format, const uns
|
||||
++i;
|
||||
}
|
||||
|
||||
// debug
|
||||
// std::string debug = "[RewriteFormatString] RAW=|" + real_format + "|, OUT=|" + out + "|\n";
|
||||
// std::fwrite(debug.c_str(), 1, debug.size(), stdout);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -456,7 +671,7 @@ void ServerLogger::LogImpl(uns::ServerLogLevel level, const std::string& format,
|
||||
|
||||
std::string logstr = GenerateLogHeader(level);
|
||||
logstr += formatted;
|
||||
logstr += "\n";
|
||||
logstr += "\033[0m\n";
|
||||
|
||||
std::unique_lock<std::mutex> lock(QueueMutex);
|
||||
LogQueue.push_back(logstr);
|
||||
@@ -497,7 +712,7 @@ void ServerLogger::LogFImpl(uns::ServerLogLevel level, const std::string& filena
|
||||
|
||||
std::string logstr = GenerateLogHeader(level);
|
||||
logstr += formatted;
|
||||
logstr += "\n";
|
||||
logstr += "\033[0m\n";
|
||||
|
||||
std::unique_lock<std::mutex> lock(QueueMutex);
|
||||
LogQueue.push_back(logstr);
|
||||
@@ -527,7 +742,7 @@ void ServerLogger::LogFMTImpl(uns::ServerLogLevel level, const std::string& form
|
||||
|
||||
std::string logstr = GenerateLogHeader(level);
|
||||
logstr += formatted;
|
||||
logstr += "\n";
|
||||
logstr += "\033[0m\n";
|
||||
|
||||
std::unique_lock<std::mutex> lock(QueueMutex);
|
||||
LogQueue.push_back(logstr);
|
||||
@@ -557,7 +772,7 @@ void ServerLogger::LogFMT_FImpl(uns::ServerLogLevel level, const std::string& fi
|
||||
|
||||
std::string logstr = GenerateLogHeader(level);
|
||||
logstr += formatted;
|
||||
logstr += "\n";
|
||||
logstr += "\033[0m\n";
|
||||
|
||||
std::unique_lock<std::mutex> lock(QueueMutex);
|
||||
LogQueue.push_back(logstr);
|
||||
|
||||
@@ -91,7 +91,7 @@ void ServerProcessor::AppenedBlockedIP(DateTime::Span block_time, std::string ip
|
||||
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());
|
||||
SCLOGF_INFO("IP: [{}] has been blocked untill {{{}}}", ip, std::string(expr_time));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,6 +136,11 @@ bool ServerProcessor::IsPathSafe(const std::string& raw_path)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ServerProcessor::IsHeaderValid(uns::RequestPtr request)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
uns::ResponsePtr ServerProcessor::Handle(uns::RequestPtr request)
|
||||
{
|
||||
std::string x_real_ip;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include "Global.h"
|
||||
#include "Export.h"
|
||||
#include "IPTable.h"
|
||||
@@ -23,9 +23,14 @@ public:
|
||||
void AddStreamSettings(std::string method, bool stream);
|
||||
|
||||
public:
|
||||
// 主接口,重载以实现对请求的处理
|
||||
virtual uns::ResponsePtr Processor(uns::RequestPtr request) = 0;
|
||||
// 路径穿越配置,重载以配置允许的路径穿越类型
|
||||
virtual uns::PathTraversalDefenceLevel PTDefence();
|
||||
// 路径穿越防护,重载以实现路径穿越检查,配置为AutoNormalize或AllowNormal时必须,否则自动退化为DenyAll
|
||||
virtual bool IsPathSafe(const std::string& raw_path);
|
||||
// 请求头预检,重载以实现在接收请求体之前检查请求头,返回false则服务器将强制关闭连接
|
||||
virtual bool IsHeaderValid(uns::RequestPtr request);
|
||||
|
||||
public:
|
||||
uns::ResponsePtr Handle(uns::RequestPtr request);
|
||||
|
||||
@@ -13,7 +13,8 @@ public:
|
||||
bool HTMLResponse = false;
|
||||
std::string TempRoot;
|
||||
IPTablePtr BlockedIPs = nullptr;
|
||||
WebFileInfoVec FileInfo;
|
||||
TempFileManager FileManager;
|
||||
std::chrono::seconds FileTimeout = 300s, FileMaxProcTimeout = 600s;
|
||||
};
|
||||
|
||||
SyncFileReceiver::SyncFileReceiver() : pimpl(std::make_unique<Impl>())
|
||||
@@ -23,31 +24,34 @@ 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)
|
||||
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_info.size());
|
||||
|
||||
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;
|
||||
SCLOG_DEBUG("SyncFileReceiver init mode: %s", (html ? "html" : "json"));
|
||||
SCLOGF_DEBUG("SyncFileReceiver init mode: {}", (html ? "html" : "json"));
|
||||
}
|
||||
|
||||
void SyncFileReceiver::SetCORSEnable(bool enable)
|
||||
{
|
||||
pimpl->EnableCORS = enable;
|
||||
SCLOG_DEBUG("SyncFileReceiver CORS mode: %s", (enable ? "enabled" : "disabled"));
|
||||
SCLOGF_DEBUG("SyncFileReceiver CORS mode: {}", (enable ? "enabled" : "disabled"));
|
||||
}
|
||||
|
||||
void SyncFileReceiver::SetTempRoot(std::string temp_root)
|
||||
{
|
||||
pimpl->TempRoot = temp_root;
|
||||
SCLOG_TRACE("SFR-TempRoot: %s", pimpl->TempRoot.c_str());
|
||||
if (pimpl->FileManager.SetBaseDirectory(temp_root))
|
||||
SCLOGF_TRACE("SFR-TempRoot: {}", pimpl->TempRoot);
|
||||
else
|
||||
SCLOGF_WARNING("SFR: Failed to Set Temp Root ({})", temp_root);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,7 +67,7 @@ void SyncFileReceiver::AppenedBlockedIP(DateTime::Span block_time, std::string i
|
||||
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());
|
||||
SCLOGF_INFO("IP: [{}] has been blocked untill {{{}}}", ip, std::string(expr_time));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,13 +101,24 @@ bool SyncFileReceiver::WriteFile(const std::string& path, const std::string& byt
|
||||
std::ofstream stream{ path, std::ios::binary };
|
||||
if (stream.fail())
|
||||
{
|
||||
SCLOG_WARNING("Failed to write file [%s]: can't open stream", path.c_str());
|
||||
SCLOGF_WARNING("Failed to write file [{}]: can't open stream", path);
|
||||
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();
|
||||
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()
|
||||
@@ -111,25 +126,41 @@ uns::PathTraversalDefenceLevel SyncFileReceiver::PTDefence()
|
||||
return uns::PathTraversalDefenceLevel::DenyAll;
|
||||
}
|
||||
|
||||
bool SyncFileReceiver::IsPathSafe(const std::string & raw_path)
|
||||
bool SyncFileReceiver::IsPathSafe(const std::string& raw_path)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SyncFileReceiver::IsHeaderValid(uns::RequestPtr request)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
try
|
||||
{
|
||||
Json::Value sub;
|
||||
sub["FileName"] = ele.GetStorageFileName();
|
||||
sub["UploadTime"] = ele.GetUploadTime().GetTimeStamp();
|
||||
root["AcceptedFiles"].append(sub);
|
||||
Json::Value root;
|
||||
Json::FastWriter writer;
|
||||
// 1. 从管理器安全获取当前所有文件的快照
|
||||
auto file_infos = pimpl->FileManager.GetAllFileInfos();
|
||||
// 2. 组装 JSON 数据
|
||||
root["AcceptedCount"] = static_cast<Json::Value::UInt64>(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\":[]}";
|
||||
}
|
||||
return writer.write(root);
|
||||
}
|
||||
|
||||
std::string SyncFileReceiver::EncodeUploadResultHTML()
|
||||
@@ -150,44 +181,60 @@ std::string SyncFileReceiver::EncodeUploadResultHTML()
|
||||
</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;
|
||||
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") + "}<br>";
|
||||
// 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<long long>(file_infos.size()), tmp.c_str());
|
||||
if (written > 0)
|
||||
{
|
||||
result_str.resize(written); // 裁剪掉尾部多余的 \0
|
||||
return result_str;
|
||||
}
|
||||
return "HTML generation failed";
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// 异常安全兜底
|
||||
return "<html><body><center><h1>Upload Result Error</h1></center></body></html>";
|
||||
}
|
||||
}
|
||||
|
||||
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"))
|
||||
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());
|
||||
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)
|
||||
if (status != PathTraversal::UrlSafetyStatus::Safe)
|
||||
SCLOGF_WARNING("PathTraversal Detected: {}, Level: {}", path, PathTraversal::ToString(status));
|
||||
switch(PTDefence())
|
||||
switch (PTDefence())
|
||||
{
|
||||
case uns::PathTraversalDefenceLevel::DenyAll:
|
||||
if(status != PathTraversal::UrlSafetyStatus::Safe)
|
||||
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)
|
||||
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()());
|
||||
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));
|
||||
@@ -195,11 +242,11 @@ uns::ResponsePtr SyncFileReceiver::Execute(uns::RequestPtr request)
|
||||
}
|
||||
case uns::PathTraversalDefenceLevel::AllowNormal:
|
||||
{
|
||||
if(status == PathTraversal::UrlSafetyStatus::EvasiveTraversal)
|
||||
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()());
|
||||
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));
|
||||
@@ -217,13 +264,14 @@ uns::ResponsePtr SyncFileReceiver::Execute(uns::RequestPtr request)
|
||||
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()());
|
||||
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())
|
||||
@@ -236,17 +284,13 @@ uns::ResponsePtr SyncFileReceiver::Execute(uns::RequestPtr request)
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
// 【修改的关键步骤】
|
||||
// 1. 同步调用虚函数,获取具体的业务处理结果(及构筑好的自定义 HTTP Response)
|
||||
uns::ResponsePtr response = ProcessFiles(pimpl->FileInfo, pimpl->TempRoot, request);
|
||||
|
||||
// 2. 清理当前类中的文件缓存(防止污染下一次 HTTP 请求)
|
||||
pimpl->FileInfo.clear();
|
||||
|
||||
// 3. 作为最后一步直接返回
|
||||
// 同步调用虚函数,获取具体的业务处理结果(及构筑好的自定义 HTTP Response)
|
||||
uns::ResponsePtr response = ProcessFiles(pimpl->FileManager, fmap, pimpl->TempRoot, request);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include "Export.h"
|
||||
#include "Global.h"
|
||||
#include "IPTable.h"
|
||||
#include "WebFileInfo.h"
|
||||
#include "HTTPObjects.h"
|
||||
#include "TempFileManager.h"
|
||||
|
||||
using SFR_FileMap = std::map<std::string, std::string>;
|
||||
|
||||
class UNSWSC_DLL_EXPORT SyncFileReceiver
|
||||
{
|
||||
@@ -25,16 +27,22 @@ public:
|
||||
uns::HTTPMethod GetMethod(uns::RequestPtr request);
|
||||
void AppenedBlockedIP(DateTime::Span block_time, std::string ip);
|
||||
bool WriteFile(const std::string& path, const std::string& bytes);
|
||||
void SetFileTimeout(std::chrono::seconds timeout = 0s, std::chrono::seconds max_proc_timeout = 0s) noexcept;
|
||||
|
||||
public:
|
||||
// 请求信息预检,重载以在保存文件之前检查请求体
|
||||
virtual uns::Status PreCheckRequest(uns::RequestPtr request) = 0;
|
||||
// 表单预检,重载以在处理表单前检查表单
|
||||
virtual bool PreCheckForm(uns::FormPartPtr form) = 0;
|
||||
// 路径穿越配置,重载以配置允许的路径穿越类型
|
||||
virtual uns::PathTraversalDefenceLevel PTDefence();
|
||||
// 路径穿越防护,重载以实现路径穿越检查,配置为AutoNormalize或AllowNormal时必须,否则自动退化为DenyAll
|
||||
virtual bool IsPathSafe(const std::string& raw_path);
|
||||
// 请求头预检,重载以实现在接收请求体之前检查请求头,返回false则服务器将强制关闭连接
|
||||
virtual bool IsHeaderValid(uns::RequestPtr request);
|
||||
|
||||
// 【修改】由原先的 Callback 改为可供子类重写的虚函数
|
||||
// 返回值改为 webcc::ResponsePtr,并且引入 request 参数以便子类调用 AutoCORS 或解析请求头
|
||||
virtual uns::ResponsePtr ProcessFiles(const WebFileInfoVec& file_info, const std::string& tmp_root, uns::RequestPtr request);
|
||||
// 主接口,重载以接收并处理文件
|
||||
virtual uns::ResponsePtr ProcessFiles(TempFileManager& file_info, SFR_FileMap file_map, const std::string& tmp_root, uns::RequestPtr request);
|
||||
|
||||
public:
|
||||
uns::ResponsePtr Execute(uns::RequestPtr request);
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
#include "TempFileManager.h"
|
||||
#include <filesystem>
|
||||
#include <system_error>
|
||||
#include "ServerLogger.h"
|
||||
|
||||
TempFileManager::TempFileManager() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
// ================= 更新:核心析构函数实现 =================
|
||||
TempFileManager::~TempFileManager() noexcept
|
||||
{
|
||||
// 1. 必须第一步:发送停止信号并阻塞等待后台清理线程彻底退出
|
||||
// 这样能确保后面遍历 file_map 时,绝对没有第二个线程在并发访问它
|
||||
SCLOG_INFO("TempFileManager Destruction Begin");
|
||||
cleanup_thread.request_stop();
|
||||
if (cleanup_thread.joinable())
|
||||
cleanup_thread.join();
|
||||
// 2. 此时属于单线程环境,无需加锁。遍历并销毁所有非永久文件
|
||||
std::error_code ec;
|
||||
for (const auto& [_, fcb] : file_map)
|
||||
{
|
||||
if (!fcb.is_permanent && !base_dir.empty())
|
||||
{
|
||||
std::string path = fcb.info.MakePath(base_dir);
|
||||
// 使用无异常重载版本,即使磁盘物理删除失败(如文件被外层强行独占锁死)也绝不抛出异常
|
||||
std::filesystem::remove(path, ec);
|
||||
if (!ec)
|
||||
SCLOGF_INFO("Temp File {} Deleted.", path);
|
||||
else
|
||||
SCLOGF_INFO("Temp File {} Delete Failed: {}({})", path, ec.value(), ec.message());
|
||||
}
|
||||
// 如果 is_permanent 为 true(永久化文件),则跳过不处理,物理文件将安全留在磁盘上
|
||||
}
|
||||
// 3. 析构结束,file_map 内存控制块会自动退栈销毁
|
||||
SCLOG_INFO("TempFileManager Destruction Finished");
|
||||
}
|
||||
|
||||
// ================= 更新:高灵敏度的后台扫描逻辑 =================
|
||||
void TempFileManager::CleanupLoop(std::stop_token st) noexcept
|
||||
{
|
||||
while (!st.stop_requested())
|
||||
{
|
||||
// 改进:引入分段休眠(10次*100ms),让析构函数调用 join() 时能在最大 100ms 内瞬间响应退出
|
||||
// 避免传统的 sleep_for(1s) 导致服务器内核关闭时卡顿 1 秒
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (st.stop_requested())
|
||||
return; // 随时收到终止信号随时退出
|
||||
}
|
||||
std::unique_lock lock(rw_mutex);
|
||||
if (base_dir.empty())
|
||||
continue;
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
for (auto it = file_map.begin(); it != file_map.end(); )
|
||||
{
|
||||
bool should_delete = false;
|
||||
if (it->second.is_invalid)
|
||||
should_delete = true; //手动设置的无条件立即删除
|
||||
if (!it->second.is_permanent)
|
||||
{
|
||||
if (it->second.is_active)
|
||||
{
|
||||
if ((it->second.max_processing_timeout > std::chrono::seconds(0)) && ((now - it->second.active_start_time) > it->second.max_processing_timeout))
|
||||
should_delete = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (now > it->second.expire_time)
|
||||
should_delete = true;
|
||||
}
|
||||
}
|
||||
if (should_delete)
|
||||
{
|
||||
std::error_code ec;
|
||||
std::string path = it->second.info.MakePath(base_dir);
|
||||
std::filesystem::remove(path, ec);
|
||||
if (!ec)
|
||||
SCLOGF_INFO("Temp File {} {}, Deleted.", path, (it->second.is_invalid ? "Invalid" : "Expried"));
|
||||
else
|
||||
SCLOGF_INFO("Temp File {} {}, Delete Failed: {}({})", path, (it->second.is_invalid ? "Invalid" : "Expried"), ec.value(), ec.message());
|
||||
it = file_map.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 以下其余基础业务接口保持不变 =================
|
||||
|
||||
bool TempFileManager::SetBaseDirectory(std::string dir) noexcept
|
||||
{
|
||||
std::unique_lock lock(rw_mutex); // 加上写锁,防止与其他文件操作并发
|
||||
base_dir = std::move(dir);
|
||||
std::error_code ec;
|
||||
// 物理创建目录(无异常版本)
|
||||
std::filesystem::create_directories(base_dir, ec);
|
||||
if (ec)
|
||||
SCLOGF_INFO("TempFileManager: create_directories error: {}({})", ec.value(), ec.message());
|
||||
// 只有在线程未启动时才启动后台清理线程,确保整个生命周期只启动一次
|
||||
if (!cleanup_thread.joinable() && !ec)
|
||||
{
|
||||
cleanup_thread = std::jthread([this] (std::stop_token st)
|
||||
{
|
||||
this->CleanupLoop(st);
|
||||
});
|
||||
SCLOGF_INFO("TempFileManager: Thread Started.");
|
||||
}
|
||||
else
|
||||
SCLOGF_ERROR("TempFileManager: Failed To Start Thread (Thread Joinable: {}, Error: {})", cleanup_thread.joinable(), ec.message());
|
||||
return !ec;
|
||||
}
|
||||
|
||||
bool TempFileManager::RegisterFile(const WebFileInfo& info, std::chrono::seconds timeout, std::chrono::seconds max_proc_timeout) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
std::unique_lock lock(rw_mutex);
|
||||
if (file_map.contains(info.GetStorageFileName()))
|
||||
{
|
||||
SCLOGF_INFO("RegisterFile Failed: File {} Already Exists", info.GetStorageFileName());
|
||||
return false;
|
||||
}
|
||||
FileControlBlock fcb
|
||||
{
|
||||
.info = info,
|
||||
.expire_time = std::chrono::steady_clock::now() + timeout,
|
||||
.remaining_timeout = timeout,
|
||||
.max_processing_timeout = max_proc_timeout
|
||||
};
|
||||
file_map[info.GetStorageFileName()] = std::move(fcb);
|
||||
SCLOGF_INFO("File {}({}) Registered", info.GetOriginalFileName(), info.GetStorageFileName());
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
SCLOG_INFO("RegisterFile Failed: Exception");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TempFileManager::InvalidateFile(const WebFileInfo& info)
|
||||
{
|
||||
std::unique_lock lock(rw_mutex);
|
||||
if (file_map.contains(info.GetStorageFileName()))
|
||||
{
|
||||
file_map[info.GetStorageFileName()].is_invalid = true;
|
||||
SCLOGF_INFO("File [{}]({}) Marked AS Invalid", info.GetStorageFileName(), info.GetOriginalFileName());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
SCLOGF_WARNING("InvalidateFile({}/{}) Failed: Not Found", info.GetOriginalFileName(), info.GetStorageFileName());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TempFileManager::InvalidateFile(const std::map<std::string, std::string>& info)
|
||||
{
|
||||
size_t succ_cnt = 0;
|
||||
std::unique_lock lock(rw_mutex);
|
||||
for (const auto& [on, sn] : info)
|
||||
{
|
||||
if (file_map.contains(sn))
|
||||
{
|
||||
file_map[sn].is_invalid = true;
|
||||
succ_cnt++;
|
||||
SCLOGF_INFO("File [{}]({}) Marked AS Invalid", sn, on);
|
||||
}
|
||||
else
|
||||
SCLOGF_WARNING("InvalidateFile({}/{}) Failed: Not Found", on, sn);
|
||||
}
|
||||
return (succ_cnt == info.size());
|
||||
}
|
||||
|
||||
bool TempFileManager::CopyFileTo(const std::string& storage_name, const std::string& dest_absolute_path) noexcept
|
||||
{
|
||||
std::shared_lock lock(rw_mutex);
|
||||
auto it = file_map.find(storage_name);
|
||||
auto dest_file = std::filesystem::path(dest_absolute_path) / storage_name;
|
||||
if (it == file_map.end())
|
||||
{
|
||||
SCLOGF_INFO("CopyFileTo({}, {}) Failed: File Not Exists", storage_name, dest_file);
|
||||
return false;
|
||||
}
|
||||
std::error_code ec;
|
||||
std::filesystem::copy(it->second.info.MakePath(base_dir), dest_file, std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec)
|
||||
SCLOGF_WARNING("CopyFileTo({}, {}) Failed: {}({})", storage_name, dest_file, ec.value(), ec.message());
|
||||
else
|
||||
SCLOGF_INFO("CopyFileTo({}, {}) Success", storage_name, dest_file);
|
||||
return !ec;
|
||||
}
|
||||
|
||||
bool TempFileManager::CutFileTo(const std::string& storage_name, const std::string& dest_absolute_path) noexcept
|
||||
{
|
||||
std::unique_lock lock(rw_mutex);
|
||||
auto it = file_map.find(storage_name);
|
||||
auto dest_file = std::filesystem::path(dest_absolute_path) / storage_name;
|
||||
if (it == file_map.end())
|
||||
{
|
||||
SCLOGF_INFO("CutFileTo({}, {}) Failed: File Not Exists", storage_name, dest_file);
|
||||
return false;
|
||||
}
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(it->second.info.MakePath(base_dir), dest_file, ec);
|
||||
if (ec)
|
||||
SCLOGF_WARNING("CutFileTo({}, {}) Failed: {}({})", storage_name, dest_file, ec.value(), ec.message());
|
||||
else
|
||||
SCLOGF_INFO("CutFileTo({}, {}) Success", storage_name, dest_file);
|
||||
if (ec)
|
||||
return false;
|
||||
file_map.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TempFileManager::RenameFile(const std::string& old_storage_name, const std::string& new_storage_name) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
std::unique_lock lock(rw_mutex);
|
||||
auto it = file_map.find(old_storage_name);
|
||||
if ((it == file_map.end()) || file_map.contains(new_storage_name))
|
||||
{
|
||||
SCLOGF_INFO("RenameFile({}, {}) Failed: File Not Exists", old_storage_name, new_storage_name);
|
||||
return false;
|
||||
}
|
||||
std::error_code ec;
|
||||
std::string old_path = it->second.info.MakePath(base_dir);
|
||||
FileControlBlock fcb = std::move(it->second);
|
||||
fcb.info.SetStorageFileName(new_storage_name);
|
||||
std::string new_path = fcb.info.MakePath(base_dir);
|
||||
std::filesystem::rename(old_path, new_path, ec);
|
||||
if (ec)
|
||||
SCLOGF_WARNING("RenameFile({}, {}) Failed: {}({})", old_storage_name, new_storage_name, ec.value(), ec.message());
|
||||
else
|
||||
SCLOGF_INFO("RenameFile({}, {}) Success", old_storage_name, new_storage_name);
|
||||
if (ec)
|
||||
return false;
|
||||
file_map.erase(it);
|
||||
file_map[new_storage_name] = std::move(fcb);
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
SCLOG_INFO("RenameFile Failed: Exception");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TempFileManager::DeleteFile(const std::string& storage_name) noexcept
|
||||
{
|
||||
std::unique_lock lock(rw_mutex);
|
||||
auto it = file_map.find(storage_name);
|
||||
if (it == file_map.end())
|
||||
{
|
||||
SCLOGF_WARNING("DeleteFile({}) Failed: File Not Exists", storage_name);
|
||||
return false;
|
||||
}
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(it->second.info.MakePath(base_dir), ec);
|
||||
if (ec)
|
||||
SCLOGF_WARNING("DeleteFile({}) Failed: {}({})", storage_name, ec.value(), ec.message());
|
||||
else
|
||||
SCLOGF_INFO("DeleteFile({}) Success", storage_name);
|
||||
if (ec)
|
||||
return false;
|
||||
file_map.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TempFileManager::ActiveFile(const std::string& storage_name) noexcept
|
||||
{
|
||||
std::unique_lock lock(rw_mutex);
|
||||
auto it = file_map.find(storage_name);
|
||||
if ((it == file_map.end()) || it->second.is_active)
|
||||
{
|
||||
SCLOGF_WARNING("ActiveFile({}) Failed: {}", storage_name, (it->second.is_active ? "Already Actived" : "File Not Exists"));
|
||||
return false;
|
||||
}
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (it->second.expire_time > now)
|
||||
it->second.remaining_timeout = std::chrono::duration_cast<std::chrono::seconds>(it->second.expire_time - now);
|
||||
else
|
||||
it->second.remaining_timeout = std::chrono::seconds(0);
|
||||
it->second.is_active = true;
|
||||
it->second.active_start_time = now;
|
||||
SCLOGF_INFO("ActiveFile({}) Success", storage_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TempFileManager::DeactiveFile(const std::string& storage_name) noexcept
|
||||
{
|
||||
std::unique_lock lock(rw_mutex);
|
||||
auto it = file_map.find(storage_name);
|
||||
if ((it == file_map.end()) || !it->second.is_active)
|
||||
{
|
||||
SCLOGF_WARNING("DeactiveFile({}) Failed: {}", storage_name, ((it != file_map.end()) ? "Already Deactived" : "File Not Exists"));
|
||||
return false;
|
||||
}
|
||||
it->second.is_active = false;
|
||||
it->second.expire_time = std::chrono::steady_clock::now() + it->second.remaining_timeout;
|
||||
SCLOGF_INFO("DeactiveFile({}) Success", storage_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TempFileManager::SetPermanent(const std::string& storage_name, bool permanent) noexcept
|
||||
{
|
||||
std::unique_lock lock(rw_mutex);
|
||||
auto it = file_map.find(storage_name);
|
||||
if (it == file_map.end())
|
||||
{
|
||||
SCLOGF_WARNING("SetPermanent({}) Failed: File Not Exists", storage_name);
|
||||
return false;
|
||||
}
|
||||
it->second.is_permanent = permanent;
|
||||
SCLOGF_INFO("SetPermanent({}) Success", storage_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TempFileManager::FileExists(const std::string& storage_name) const noexcept
|
||||
{
|
||||
std::shared_lock lock(rw_mutex);
|
||||
return file_map.contains(storage_name);
|
||||
}
|
||||
|
||||
bool TempFileManager::GetFileInfo(const std::string& storage_name, WebFileInfo& out_info) const noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
std::shared_lock lock(rw_mutex);
|
||||
auto it = file_map.find(storage_name);
|
||||
if (it == file_map.end())
|
||||
return false;
|
||||
out_info = it->second.info;
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
SCLOGF_INFO("GetFileInfo({}) Failed: Exception", storage_name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t TempFileManager::GetFileCount() const noexcept
|
||||
{
|
||||
return file_map.size();
|
||||
}
|
||||
|
||||
std::vector<WebFileInfo> TempFileManager::GetAllFileInfos() const noexcept
|
||||
{
|
||||
std::vector<WebFileInfo> list;
|
||||
try
|
||||
{
|
||||
std::shared_lock lock(rw_mutex); // 申请读锁,支持高并发并发读取
|
||||
list.reserve(file_map.size()); // 提前预留空间,减少内存重分配次数
|
||||
for (const auto& [_, fcb] : file_map)
|
||||
list.push_back(fcb.info); // 拷贝文件元数据到外部
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
list.clear(); // 极端内存崩溃(bad_alloc)时,清空并安全返回空数组
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include "Export.h"
|
||||
#include <shared_mutex>
|
||||
#include "WebFileInfo.h"
|
||||
#include <unordered_map>
|
||||
|
||||
using std::chrono::operator""s;
|
||||
using std::chrono::operator""min;
|
||||
|
||||
class UNSWSC_DLL_EXPORT TempFileManager
|
||||
{
|
||||
private:
|
||||
struct FileControlBlock
|
||||
{
|
||||
WebFileInfo info;
|
||||
std::chrono::steady_clock::time_point expire_time;
|
||||
std::chrono::steady_clock::time_point active_start_time;
|
||||
std::chrono::seconds remaining_timeout;
|
||||
std::chrono::seconds max_processing_timeout;
|
||||
bool is_active = false;
|
||||
bool is_permanent = false; // 是否永久保留
|
||||
bool is_invalid = false;
|
||||
};
|
||||
|
||||
std::string base_dir;
|
||||
std::unordered_map<std::string, FileControlBlock> file_map;
|
||||
mutable std::shared_mutex rw_mutex;
|
||||
|
||||
// C++20 jthread
|
||||
std::jthread cleanup_thread;
|
||||
|
||||
void CleanupLoop(std::stop_token st) noexcept;
|
||||
|
||||
public:
|
||||
TempFileManager() noexcept;
|
||||
~TempFileManager() noexcept;
|
||||
|
||||
// 禁止拷贝与移动
|
||||
TempFileManager(const TempFileManager&) = delete;
|
||||
TempFileManager& operator=(const TempFileManager&) = delete;
|
||||
TempFileManager(TempFileManager&&) = delete;
|
||||
TempFileManager& operator=(TempFileManager&&) = delete;
|
||||
|
||||
// 基础业务接口(保持不变)
|
||||
bool SetBaseDirectory(std::string dir) noexcept;
|
||||
bool RegisterFile(const WebFileInfo& info, std::chrono::seconds timeout, std::chrono::seconds max_proc_timeout = std::chrono::seconds(0)) noexcept;
|
||||
bool InvalidateFile(const WebFileInfo& info);
|
||||
//For SFR_FileMap<OriginFileName, StorageFileName>
|
||||
bool InvalidateFile(const std::map<std::string, std::string>& info);
|
||||
bool CopyFileTo(const std::string& storage_name, const std::string& dest_absolute_path) noexcept;
|
||||
bool CutFileTo(const std::string& storage_name, const std::string& dest_absolute_path) noexcept;
|
||||
bool RenameFile(const std::string& old_storage_name, const std::string& new_storage_name) noexcept;
|
||||
bool DeleteFile(const std::string& storage_name) noexcept;
|
||||
bool ActiveFile(const std::string& storage_name) noexcept;
|
||||
bool DeactiveFile(const std::string& storage_name) noexcept;
|
||||
bool SetPermanent(const std::string& storage_name, bool permanent) noexcept;
|
||||
bool FileExists(const std::string& storage_name) const noexcept;
|
||||
bool GetFileInfo(const std::string& storage_name, WebFileInfo& out_info) const noexcept;
|
||||
size_t GetFileCount() const noexcept;
|
||||
// 获取当前所有管理中的文件信息快照(线程安全,绝不抛出异常)
|
||||
std::vector<WebFileInfo> GetAllFileInfos() const noexcept;
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
// 确保引入包含日志宏和 GlobalServerLogger 的头文件
|
||||
#include "ServerLogger.h"
|
||||
|
||||
#ifndef __FILENAME__
|
||||
#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__)
|
||||
#endif
|
||||
|
||||
// 仅用于提供函数指针地址
|
||||
void DummyPureFunction() {}
|
||||
|
||||
int main() {
|
||||
// 【步骤 1】初始化控制台日志,开启最低级别
|
||||
SCLOG_CONSOLE_INIT(uns::llTrace);
|
||||
|
||||
SCLOG_SHORT_INFO("=== ServerLogger Macro-based Test Started ===");
|
||||
|
||||
// =================================================================
|
||||
// 1. C++ fmt 风格测试 (带 F 的宏)
|
||||
// =================================================================
|
||||
std::cout << "\n--- [Part 1: FMT Style Macros Test] ---" << std::endl;
|
||||
{
|
||||
// 基础类型
|
||||
SCLOGF_INFO("Base values (Long): int={}, double={}, string={}, bool={}, wstring={}",
|
||||
42, 3.14159, std::string("UNS_Core日志测试"), true, L"wstring防乱码测试");
|
||||
SCLOGF_SHORT_DEBUG("Base values (Short): int={}, string={}",
|
||||
1024, "ShortFormatTest");
|
||||
|
||||
// 容器 (Range)
|
||||
std::vector<int> dummy_vector = {10, 20, 30, 40};
|
||||
SCLOGF_DEBUG("Vector lazy contents: {}", dummy_vector);
|
||||
|
||||
// 键值对 (Pair)
|
||||
std::pair<std::string, double> dummy_pair = {"Database_Connections", 12.0};
|
||||
SCLOGF_WARNING("Metric data pair: {}", dummy_pair);
|
||||
|
||||
// 函数/闭包 (Function)
|
||||
std::function<void()> func1(DummyPureFunction);
|
||||
auto dummy_closure = [status = 500]() { return status; };
|
||||
std::function func2 = dummy_closure;
|
||||
SCLOGF_ERROR("Registered handlers - Pure: {}, Closure: {}", func1, func2);
|
||||
|
||||
// 混合复杂编排
|
||||
std::vector<int> scores = {99, 95, 88};
|
||||
SCLOGF_FATAL("Critical failure! Operator: '{}', Cluster Nodes: {}, Error Code: {}",
|
||||
"RootAdmin", scores, 5005);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 2. 传统 printf 风格测试 (不带 F 的宏)
|
||||
// =================================================================
|
||||
std::cout << "\n--- [Part 2: Printf Style Macros Test] ---" << std::endl;
|
||||
{
|
||||
// 2.1 测试带文件行号的长日志宏 (SCLOG_xxx)
|
||||
SCLOG_TRACE("Printf Trace log: msg=%s, val=%d", "TraceMessage", 100);
|
||||
SCLOG_DEBUG("Printf Debug log: score=%.2f", 98.5);
|
||||
SCLOG_INFO("Printf Info log: hex=0x%X", 0xAB);
|
||||
SCLOG_WARNING("Printf Warning log: active=%s", "true");
|
||||
SCLOG_ERROR("Printf Error log: connection count=%d", 5);
|
||||
SCLOG_FATAL("Printf Fatal log: system exit code=%d", -1);
|
||||
|
||||
// 2.2 测试不带文件行号的短日志宏 (SCLOG_SHORT_xxx)
|
||||
SCLOG_SHORT_TRACE("Printf Short Trace: code=%d", 1);
|
||||
SCLOG_SHORT_DEBUG("Printf Short Debug: name=%s", "WorkerA");
|
||||
SCLOG_SHORT_INFO("Printf Short Info: load=%d%%", 85);
|
||||
SCLOG_SHORT_WARNING("Printf Short Warning: threshold=%.1f", 90.0);
|
||||
SCLOG_SHORT_ERROR("Printf Short Error: mask=0x%x", 0xFF00);
|
||||
SCLOG_SHORT_FATAL("Printf Short Fatal: panic id=%d", 999);
|
||||
}
|
||||
|
||||
std::cout << "\n" << std::endl;
|
||||
SCLOG_SHORT_INFO("=== ServerLogger Macro-based Test Completed ===");
|
||||
|
||||
for(int i = 5; i > 0; i--)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
SCLOGF_SHORT_INFO("Close Log Stream in {} Seconds", i);
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
|
||||
// 【步骤 2】退出前关闭日志流
|
||||
SCLOG_CLOSE();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -740,18 +740,18 @@ uns::ResponseBuilder& uns::ResponseBuilder::AutoCORS(uns::RequestPtr req)
|
||||
std::string host = req->GetImpl()->webcc_req->HasHeader("Host") ? req->GetImpl()->webcc_req->GetHeader("Host") : "";
|
||||
if(host.empty() || GlobalCORSConfig.HostValidate(host))
|
||||
{
|
||||
SCLOG_INFO("Applied CORS headers for origin=%s cred=%d", origin.c_str(), GlobalCORSConfig.AllowCookie() ? 1 : 0);
|
||||
SCLOGF_INFO("Applied CORS headers for origin={} cred={}", origin, GlobalCORSConfig.AllowCookie() ? 1 : 0);
|
||||
return CORS(origin);
|
||||
}
|
||||
else
|
||||
{
|
||||
SCLOG_WARNING("Actual request: Host not allowed: %s", host.c_str());
|
||||
SCLOGF_WARNING("Actual request: Host not allowed: {}", host);
|
||||
return Forbidden().Body(std::string("CORS ERROR"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SCLOG_WARNING("Actual request: Origin not allowed: %s", origin.c_str());
|
||||
SCLOGF_WARNING("Actual request: Origin not allowed: {}", origin);
|
||||
return Forbidden().Body(std::string("CORS ERROR"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,9 @@
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<TargetName>$(ProjectName)d</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
@@ -174,6 +177,7 @@
|
||||
<ClInclude Include="ServerProcessor.h" />
|
||||
<ClInclude Include="SessionManager.h" />
|
||||
<ClInclude Include="SyncFileReceiver.h" />
|
||||
<ClInclude Include="TempFileManager.h" />
|
||||
<ClInclude Include="UNSResponseBuilder.h" />
|
||||
<ClInclude Include="WebFileInfo.h" />
|
||||
</ItemGroup>
|
||||
@@ -203,6 +207,7 @@
|
||||
<ClCompile Include="ServerProcessor.cpp" />
|
||||
<ClCompile Include="SessionManager.cpp" />
|
||||
<ClCompile Include="SyncFileReceiver.cpp" />
|
||||
<ClCompile Include="TempFileManager.cpp" />
|
||||
<ClCompile Include="UNSResponseBuilder.cpp" />
|
||||
<ClCompile Include="WebFileInfo.cpp" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -84,6 +84,9 @@
|
||||
<ClInclude Include="WebFileInfo.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TempFileManager.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
@@ -152,5 +155,8 @@
|
||||
<ClCompile Include="WebFileInfo.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TempFileManager.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -90,3 +90,13 @@ size_t WebFileInfo::GetFileSize() const
|
||||
{
|
||||
return FileSize;
|
||||
}
|
||||
|
||||
void WebFileInfo::SetStorageFileName(const std::string& sfn)
|
||||
{
|
||||
StorageFileName = sfn;
|
||||
}
|
||||
|
||||
void WebFileInfo::SetOriginalFileName(const std::string& ofn)
|
||||
{
|
||||
OriginalFileName = ofn;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ public:
|
||||
std::string GetExtensionName() const;
|
||||
DateTime GetUploadTime() const;
|
||||
size_t GetFileSize() const;
|
||||
|
||||
public:
|
||||
void SetStorageFileName(const std::string& sfn);
|
||||
void SetOriginalFileName(const std::string& ofn);
|
||||
};
|
||||
|
||||
using WebFileInfoVec = std::vector<WebFileInfo>;
|
||||
Reference in New Issue
Block a user