diff --git a/TestLogger/TestLogger.cpp b/TestLogger/TestLogger.cpp new file mode 100644 index 0000000..6ff9423 --- /dev/null +++ b/TestLogger/TestLogger.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include + +// 确保引入包含日志宏和 GlobalServerLogger 的头文件 +#include "../UNSWebServerCore/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 的宏) + // ================================================================= + SCLOG_SHORT_TRACE("\n--- [Part 1: FMT Style Macros Test] ---"); + { + // 基础类型 + 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={}, long={}, string={}", + 1024, 2147483648, "ShortFormatTest"); + + // 容器 (Range) + std::vector dummy_vector = {10, 20, 30, 40}; + SCLOGF_DEBUG("Vector lazy contents: {}", dummy_vector); + + // 键值对 (Pair) + std::pair dummy_pair = {"Database_Connections", 12.0}; + SCLOGF_WARNING("Metric data pair: {}", dummy_pair); + + // 函数/闭包 (Function) + std::function func1(DummyPureFunction); + auto dummy_closure = [status = 500]() { return status; }; + std::function func2 = dummy_closure; + SCLOGF_ERROR("Registered handlers - Pure: {}, Closure: {}", func1, func2); + + // 时间 + using Clock = std::chrono::system_clock; + // 1. 获取当前系统时间 + Clock::time_point current_time = Clock::now(); + // 2. 测试不同精度的时间点 + Clock::time_point future_time = current_time + std::chrono::hours(24); + // 3. 纯粹的时间格式化打印测试 + SCLOGF_INFO("--- Time Wrapper Verification ---"); + SCLOGF_INFO("Current System Time : {:%Y-%m-%d %H:%M:%S}", current_time); + SCLOGF_INFO("Future Task Due Time: {:%H:%M:%S}", future_time); + SCLOGF_INFO("Custom Date Format : {:%Y/%m/%d}", current_time); + + std::time_t raw_time = std::time(nullptr); + std::tm* time_info = std::localtime(&raw_time); + if (time_info != nullptr) + { + // 复制一份值对象传给日志,确保生命周期安全 + std::tm current_tm = *time_info; + SCLOGF_INFO("--- TM Struct Verification ---"); + SCLOGF_INFO("Current Time (TM) : {:%Y-%m-%d %H:%M:%S}", current_tm); + SCLOGF_INFO("Custom Date (TM) : {:%Y/%m/%d}", current_tm); + SCLOGF_INFO("Pure Time (TM) : {:%H:%M:%S}", current_tm); + } + + // 4. Duration 格式化测试(基础单位) + SCLOGF_INFO("--- Duration Wrapper Verification ---"); + + std::chrono::nanoseconds dur_ns(123); + std::chrono::microseconds dur_us(4567); + std::chrono::milliseconds dur_ms(8901); + std::chrono::seconds dur_s(65); + + // 基础单位测试 + SCLOGF_INFO("Nanoseconds : {}", dur_ns); + SCLOGF_INFO("Microseconds : {}", dur_us); + SCLOGF_INFO("Milliseconds : {}", dur_ms); + SCLOGF_INFO("Seconds : {}", dur_s); + + // 5. Duration 自动升档测试(关键) + std::chrono::seconds dur_s2(3600 + 120 + 5); // 1h 2m 5s + std::chrono::minutes dur_m(90); // 1h 30m + std::chrono::hours dur_h(48); // 2d + + SCLOGF_INFO("--- Duration Auto Scaling ---"); + SCLOGF_INFO("Mixed Seconds : {}", dur_s2); + SCLOGF_INFO("Minutes Overflow : {}", dur_m); + SCLOGF_INFO("Hours Overflow : {}", dur_h); + + // 6. 高精度 duration 测试(纳秒级) + std::chrono::nanoseconds high_ns(1234567890123LL); + + SCLOGF_INFO("--- High Precision Duration ---"); + SCLOGF_INFO("Large Nanosec : {}", high_ns); + + // 7. 负载/极值测试(防止溢出或异常) + std::chrono::nanoseconds zero_ns(0); + std::chrono::nanoseconds near_us(999); + std::chrono::nanoseconds near_ms(999999); + + SCLOGF_INFO("--- Edge Cases ---"); + SCLOGF_INFO("Zero Duration : {}", zero_ns); + SCLOGF_INFO("Near Micro Bound : {}", near_us); + SCLOGF_INFO("Near Milli Bound : {}", near_ms); + + // 混合复杂编排 + std::vector scores = {99, 95, 88}; + SCLOGF_FATAL("Critical failure! Operator: '{}', Cluster Nodes: {}, Error Code: {}", + "RootAdmin", scores, 5005); + } + + // ================================================================= + // 2. 传统 printf 风格测试 (不带 F 的宏) + // ================================================================= + SCLOG_SHORT_TRACE("\n--- [Part 2: Printf Style Macros Test] ---"); + { + // 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); + } + + SCLOG_SHORT_TRACE(""); + 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】退出前关闭日志流 + SCLOGF_SHORT_INFO("Log Stream Closed"); + SCLOG_CLOSE(); + + return 0; +} \ No newline at end of file diff --git a/TestLogger/TestLogger.vcxproj b/TestLogger/TestLogger.vcxproj new file mode 100644 index 0000000..3999373 --- /dev/null +++ b/TestLogger/TestLogger.vcxproj @@ -0,0 +1,155 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {bf89fe01-6bba-4c4c-b9b1-3daf5ef2b7f3} + TestLogger + 10.0 + + + + Application + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + Application + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;_WINDOWS;UNSWEBSERVERCORE_EXPORTS;%(PreprocessorDefinitions) + true + stdcpp20 + /utf-8 %(AdditionalOptions) + + + Console + true + + + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TestLogger/TestLogger.vcxproj.filters b/TestLogger/TestLogger.vcxproj.filters new file mode 100644 index 0000000..e0816bf --- /dev/null +++ b/TestLogger/TestLogger.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 源文件 + + + 源文件 + + + 源文件 + + + + + 头文件 + + + 头文件 + + + 头文件 + + + \ No newline at end of file diff --git a/UNSWebServerCore.slnx b/UNSWebServerCore.slnx new file mode 100644 index 0000000..f2825f4 --- /dev/null +++ b/UNSWebServerCore.slnx @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/UNSWebServerCore/CORSConfig.cpp b/UNSWebServerCore/CORSConfig.cpp new file mode 100644 index 0000000..2283994 --- /dev/null +++ b/UNSWebServerCore/CORSConfig.cpp @@ -0,0 +1,137 @@ +#include "CORSConfig.h" +#include +#include +#include +#include "Global.h" + +CORSConfig GlobalCORSConfig; + +CORSConfig::CORSConfig() +{ + max_age = 86400; + allow_cookie = false; + validate_urls = {}; + validate_hosts = {}; + validate_methods = { "GET", "POST", "OPTIONS" }; + validate_headers = + { + "Accept", + "Accept-Language", + "Content-Language", + "Content-Type", + "Authorization", + "X-Requested-With", + "X-CSRF-Token", + "X-Client-Version", + "X-App-Id", + "X-Request-ID" + }; +} + +int CORSConfig::GetMaxAge() const +{ + return max_age; +} + +bool CORSConfig::AllowCookie() const +{ + return allow_cookie; +} + +std::string CORSConfig::GetValidateMethods() const +{ + if (validate_methods.empty()) + return std::string(); + std::ostringstream oss; + size_t index = 0; + for (const auto& method : validate_methods) + { + if (index++ > 0) + oss << ", "; + oss << uns::tools::ToUpper(method); // 统一大写方法名 + } + return oss.str(); +} + +bool CORSConfig::UrlValidate(const std::string& url) const +{ + return (validate_urls.find(url) != validate_urls.end()); +} + +bool CORSConfig::HostValidate(const std::string& host) const +{ + return (validate_hosts.find(host) != validate_hosts.end()); +} + +bool CORSConfig::IsMethodAllowed(const std::string method) const +{ + return (validate_methods.find(method) != validate_methods.end()); +} + +std::string CORSConfig::GetValidateHeaders(const std::set& headers) const +{ + // build map from lowercase(validate_headers) => original form (preserve casing) + std::map lowerToOriginal; + for (const auto& h : validate_headers) + lowerToOriginal[uns::tools::ToLower(h)] = h; + std::vector allowedOut; + if (headers.empty()) + { + // 返回服务端全部允许的头(保持 validate_headers 中的原始展示形式) + for (const auto& kv : lowerToOriginal) + allowedOut.push_back(kv.second); + } + else + { + // 交集比较(headers 已经是小写的—为了健壮性,再转换一次) + for (const auto& reqLower : headers) + { + std::string reqNorm = uns::tools::ToLower(reqLower); + auto it = lowerToOriginal.find(reqNorm); + if (it != lowerToOriginal.end()) + allowedOut.push_back(it->second); // 使用原始展示形式 + } + // 如果客户端请求了头(headers 非空)但交集为空,返回空字符串 + } + if (allowedOut.empty()) + return std::string(); + // join with ", " + std::ostringstream oss; + for (size_t i = 0; i < allowedOut.size(); ++i) + { + if (i) + oss << ", "; + oss << allowedOut[i]; + } + return oss.str(); +} + +void CORSConfig::SetMaxAge(int max_age) +{ + this->max_age = max_age; +} + +void CORSConfig::SetAllowCookie(bool allow) +{ + allow_cookie = allow; +} + +void CORSConfig::AddValidateUrls(const std::string& url) +{ + validate_urls.insert(url); +} + +void CORSConfig::AddValidateHost(const std::string& host) +{ + validate_hosts.insert(host); +} + +void CORSConfig::AddValidateMethods(const std::string& method) +{ + validate_methods.insert(uns::tools::ToUpper(method)); +} + +void CORSConfig::AddValidateHeaders(const std::string& header) +{ + validate_headers.insert(header); +} diff --git a/UNSWebServerCore/CORSConfig.h b/UNSWebServerCore/CORSConfig.h new file mode 100644 index 0000000..e691996 --- /dev/null +++ b/UNSWebServerCore/CORSConfig.h @@ -0,0 +1,36 @@ +#include +#include +#include "Export.h" + +class UNSWSC_DLL_EXPORT CORSConfig +{ +private: + int max_age; + bool allow_cookie; + std::set validate_urls; + std::set validate_hosts; + std::set validate_headers; + std::set validate_methods; + +public: + CORSConfig(); + +public: + int GetMaxAge() const; + bool AllowCookie() const; + std::string GetValidateMethods() const; + bool UrlValidate(const std::string& url) const; + bool HostValidate(const std::string& host) const; + bool IsMethodAllowed(const std::string method) const; + std::string GetValidateHeaders(const std::set& headers = {}) const; + +public: + void SetMaxAge(int max_age); + void SetAllowCookie(bool allow); + void AddValidateUrls(const std::string& url); + void AddValidateHost(const std::string& host); + void AddValidateMethods(const std::string& method); + void AddValidateHeaders(const std::string& header); +}; + +extern UNSWSC_DLL_EXPORT CORSConfig GlobalCORSConfig; \ No newline at end of file diff --git a/UNSWebServerCore/CORSProcessor.cpp b/UNSWebServerCore/CORSProcessor.cpp new file mode 100644 index 0000000..ed95ee4 --- /dev/null +++ b/UNSWebServerCore/CORSProcessor.cpp @@ -0,0 +1,146 @@ +#include "CORSProcessor.h" +#include "ServerLogger.h" +#include "CORSConfig.h" +#include "UNSResponseBuilder.h" + +// ----- 辅助函数(文件作用域) ----- +static inline std::string Trim(const std::string& s) +{ + size_t start = 0; + while ((start < s.size()) && std::isspace(static_cast(s[start]))) + ++start; + if (start == s.size()) + return ""; + + size_t end = s.size() - 1; + while ((end > start) && std::isspace(static_cast(s[end]))) + --end; + return s.substr(start, end - start + 1); +} + +static inline std::vector SplitByComma(const std::string& raw) +{ + std::vector parts; + std::istringstream ss(raw); + std::string token; + while (std::getline(ss, token, ',')) + parts.push_back(token); + return parts; +} + +// 1) 解析请求头(Access-Control-Request-Headers) +// 返回小写形式的集合(用于比较/交集) +std::set CORSProcessor::ParseHeaderList(const std::string& raw_headers) +{ + std::set out; + if (raw_headers.empty()) + return out; + + auto parts = SplitByComma(raw_headers); + for (const auto& p : parts) + { + std::string t = Trim(p); + if (t.empty()) + continue; + out.insert(uns::tools::ToLower(t)); // 规范化为小写用于比较 + } + return out; +} + +// 2) 解析请求方法字符串(Access-Control-Request-Method 或逗号分隔的 Methods) +// 返回大写形式的集合(用于比较) +std::set CORSProcessor::ParseMethodList(const std::string& raw_headers) +{ + std::set out; + if (raw_headers.empty()) + return out; + + auto parts = SplitByComma(raw_headers); + for (const auto& p : parts) + { + std::string t = Trim(p); + if (t.empty()) + continue; + out.insert(uns::tools::ToUpper(t)); + } + return out; +} + +uns::ResponsePtr CORSProcessor::Processor(uns::RequestPtr request) +{ + if(request->GetMethod() != "OPTIONS") + return uns::ResponseBuilder().MethodNotAllowed().EmptyBody()(); + SCLOG_INFO("OPTIONS Request Catched!"); + + if((!request->HasHeader(uns::cors::reqh_acrm)) || (!request->HasHeader(uns::cors::reqh_o))) + return uns::ResponseBuilder().BadRequest().EmptyBody()(); + + std::string origin = Trim(request->GetHeader(uns::cors::reqh_o)); + std::string acrm = Trim(request->GetHeader(uns::cors::reqh_acrm)); + std::string acrh = request->HasHeader(uns::cors::reqh_acrh) ? Trim(request->GetHeader(uns::cors::reqh_acrh)) : std::string(); + + 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()); + return uns::ResponseBuilder().Forbidden().EmptyBody()(); + } + + if(!GlobalCORSConfig.UrlValidate(origin)) + { + SCLOG_WARNING("Invalid CORS Origin: %s", origin.c_str()); + return uns::ResponseBuilder().Forbidden().EmptyBody()(); + } + + auto acrm_values = ParseMethodList(acrm); + if (acrm_values.empty()) + { + SCLOG_WARNING("Empty Access-Control-Request-Method"); + return uns::ResponseBuilder().BadRequest().EmptyBody()(); + } + 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()); + return uns::ResponseBuilder().NotAcceptable().EmptyBody()(); + } + + auto acrh_values = acrh.empty() ? std::set() : ParseHeaderList(acrh); + if (!acrh_values.empty()) + { + const size_t MAX_HDR_COUNT = 50; + const size_t MAX_HDR_TOTAL_LEN = 4096; // 可调整 + if ((acrh_values.size() > MAX_HDR_COUNT) || (acrh.size() > MAX_HDR_TOTAL_LEN)) + { + SCLOG_WARNING("ACRH too large or too many entries"); + return uns::ResponseBuilder().BadRequest().EmptyBody()(); + } + } + + auto valid_headers = GlobalCORSConfig.GetValidateHeaders(acrh_values); + if((!acrh_values.empty()) && valid_headers.empty()) + { + SCLOG_WARNING("Invalid CORS Header(s): %s", acrh.c_str()); + return uns::ResponseBuilder().NotAcceptable().EmptyBody()(); + } + + std::string allow_methods_value = GlobalCORSConfig.GetValidateMethods(); + if (allow_methods_value.empty()) + { + SCLOG_WARNING("No allowed methods configured"); + 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); + return uns::ResponseBuilder().CORS_Full(origin, acrh_values).NoContent().EmptyBody()(); +} + +std::string CORSProcessor::UrlRegex() +{ + return R"(/[\s\S]*)"; +} + +std::shared_ptr CORSProcessor::SharedPtr() +{ + return std::make_shared(); +} \ No newline at end of file diff --git a/UNSWebServerCore/CORSProcessor.h b/UNSWebServerCore/CORSProcessor.h new file mode 100644 index 0000000..ae96b0f --- /dev/null +++ b/UNSWebServerCore/CORSProcessor.h @@ -0,0 +1,15 @@ +#include "ServerProcessor.h" + +class CORSProcessor : public ServerProcessor +{ +private: + std::set ParseHeaderList(const std::string& raw_headers); + std::set ParseMethodList(const std::string& raw_headers); + +public: + uns::ResponsePtr Processor(uns::RequestPtr request) override; + +public: + static std::string UrlRegex(); + static std::shared_ptr SharedPtr(); +}; \ No newline at end of file diff --git a/UNSWebServerCore/DataTransfer.cpp b/UNSWebServerCore/DataTransfer.cpp new file mode 100644 index 0000000..7ddcbd5 --- /dev/null +++ b/UNSWebServerCore/DataTransfer.cpp @@ -0,0 +1,180 @@ +#include "DataTransfer.h" +#include +#include "ServerLogger.h" + +DataTransfer::DataTransfer(const std::string& tr) +{ + temp_root = tr; +} + +void DataTransfer::Init(const std::string& tr) +{ + temp_root = tr; + SCLOG_DEBUG("GDT-TempRoot: %s", temp_root.c_str()); +} + +bool DataTransfer::ItemExist(const std::string& file) +{ + return (files.find(file) != files.end()); +} + +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()); + return true; +} + +bool DataTransfer::RemoveItem(const std::string& file) +{ + if (!ItemExist(file)) + return false; + files.erase(file); + SCLOG_INFO("GDT: Item [%s] Removed", file.c_str()); + return true; +} + +bool DataTransfer::ItemValidate(const std::string& file) +{ + if (!ItemExist(file)) + return false; + return files[file]; +} + +void DataTransfer::DeactivateItem(const std::string& file) +{ + if (!ItemExist(file)) + return; + files[file] = false; + SCLOG_INFO("GDT: Item [%s] Deactivated", file.c_str()); +} + +bool DataTransfer::CopyItemTo(const std::string& file, const std::string& dest_path) +{ + if (!ItemExist(file)) + return false; + namespace fs = std::filesystem; + 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()); + 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()); + return true; + } + else + { + SCLOG_ERROR("Failed To Delete File [%s], Error: %s (%d)", dest_file.c_str(), error.message().c_str(), error.value()); + return false; + } +} + +bool DataTransfer::CopyItemAS(const std::string& file, const std::string& dest) +{ + if (!ItemExist(file)) + return false; + namespace fs = std::filesystem; + std::error_code error; + 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()); + return true; + } + else + { + SCLOG_ERROR("Failed To Delete File [%s], Error: %s (%d)", dest_file.c_str(), error.message().c_str(), error.value()); + return false; + } +} + +bool DataTransfer::RemoveAllCacheFiles() +{ + SCLOG_INFO("Begin Cache Clear"); + namespace fs = std::filesystem; + bool result = true; + std::error_code error; + if (files.empty()) + { + SCLOG_INFO("No Cache File Found, Skip GDT Clear"); + goto fs_scan; + } + for (const auto& [file, status] : files) + { + std::string filepath = MakePath(file); + if (!fs::exists(filepath, error)) + { + SCLOG_WARNING("File [%s] Not Exist, Skip", filepath.c_str()); + continue; + } + if (fs::remove(filepath, error)) + SCLOG_INFO("File [%s] Deleted", filepath.c_str()); + else + { + SCLOG_ERROR("Failed To Delete File [%s], Error: %s (%d)", filepath.c_str(), error.message().c_str(), error.value()); + result = false; + } + } + if (result) + files.clear(); +fs_scan: + SCLOG_INFO("Cache Clear Process (GDT) Finished, Begin Filesystem Scan"); + int dirs = 0, files = 0, dirs_deleted = 0, files_deleted = 0; + for (const auto& entry : fs::directory_iterator(temp_root, error)) + { + if (entry.is_directory()) + { + dirs++; + try + { + if (fs::remove_all(entry.path(), error)) + { + dirs_deleted++; + SCLOG_WARNING("Found Directory [%s] In Cache Directory, Deleted", entry.path().string().c_str()); + } + 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()); + } + 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()); + } + } + else if (entry.is_regular_file()) + { + files++; + if (fs::remove(entry.path(), error)) + { + files_deleted++; + SCLOG_INFO("File [%s] Deleted", entry.path().string().c_str()); + } + else + { + SCLOG_ERROR("Failed To Delete File [%s], Error: %s (%d)", entry.path().string().c_str(), error.message().c_str(), 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); + SCLOG_INFO("Cache Clear Finished"); + return result; +} + +std::string DataTransfer::MakePath(const std::string& file) +{ + if (temp_root.empty() || (!ItemExist(file))) + return ""; + if (temp_root[temp_root.size() - 1] == '/') + return temp_root + file; + else + return temp_root + "/" + file; +} + +DataTransfer GlobalDataTransfer; \ No newline at end of file diff --git a/UNSWebServerCore/DataTransfer.h b/UNSWebServerCore/DataTransfer.h new file mode 100644 index 0000000..3438bbd --- /dev/null +++ b/UNSWebServerCore/DataTransfer.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include "Export.h" + +class UNSWSC_DLL_EXPORT DataTransfer +{ +private: + std::string temp_root; + std::map files; + +public: + DataTransfer(const std::string& tr = ""); + +public: + void Init(const std::string& tr); + bool ItemExist(const std::string& file); + bool InsertItem(const std::string& file); + bool RemoveItem(const std::string& file); + bool ItemValidate(const std::string& file); + void DeactivateItem(const std::string& file); + bool CopyItemTo(const std::string& file, const std::string& dest_path); + bool CopyItemAS(const std::string& file, const std::string& dest); + +public: + bool RemoveAllCacheFiles(); + std::string MakePath(const std::string& file); +}; + +extern UNSWSC_DLL_EXPORT DataTransfer GlobalDataTransfer; + +#define GDT_INIT(__tr__) GlobalDataTransfer.Init(__tr__) +#define GDT_ADDITEM(__file__) GlobalDataTransfer.InsertItem(__file__) +#define GDT_DELETEITEM(__file__) GlobalDataTransfer.RemoveItem(__file__) +#define GDT_ITEMEXIST(__file__) GlobalDataTransfer.ItemExist(__file__) +#define GDT_ITEMVALIDATE(__file__) GlobalDataTransfer.ItemValidate(__file__) +#define GDT_DEACTIVEITEM(__file__) GlobalDataTransfer.DeactivateItem(__file__) +#define GDT_GETFULLPATH(__file__ ) GlobalDataTransfer.MakePath(__file__) +#define GDT_CLEAR_ALL_CACHE() GlobalDataTransfer.RemoveAllCacheFiles() +#define GDT_COPY_TO(__file__, __dest_path__) GlobalDataTransfer.CopyItemTo(__file__, __dest_path__) +#define GDT_COPY_AS(__file__, __dest_path__) GlobalDataTransfer.CopyItemAS(__file__, __dest_path__) \ No newline at end of file diff --git a/UNSWebServerCore/DateTime.cpp b/UNSWebServerCore/DateTime.cpp new file mode 100644 index 0000000..c226e0a --- /dev/null +++ b/UNSWebServerCore/DateTime.cpp @@ -0,0 +1,505 @@ +#include "DateTime.h" +#include +#include +#include + +void DateTime::UpdateDT() +{ + namespace ch = std::chrono; + auto tp = ch::system_clock::from_time_t(storage); + + try + { + // C++20 时区感知:将系统时间转换为本地时间 + auto local_tp = ch::current_zone()->to_local(tp); + auto dp = ch::floor(local_tp); + + ch::year_month_day ymd { dp }; + ch::hh_mm_ss hms { local_tp - dp }; + + sep_time.year = static_cast(ymd.year()); + sep_time.month = static_cast(ymd.month()); + sep_time.day = static_cast(ymd.day()); + sep_time.hour = hms.hours().count(); + sep_time.minute = hms.minutes().count(); + sep_time.second = static_cast(hms.seconds().count()); + } + catch (...) + { + // 备用方案:如果系统未配置或不支持本地时区数据库,平滑降级到传统安全实现 + std::tm ptm {}; +#ifdef _WIN32 + localtime_s(&ptm, &storage); +#else + localtime_r(&storage, &ptm); +#endif + sep_time.year = ptm.tm_year + 1900; + sep_time.month = ptm.tm_mon + 1; + sep_time.day = ptm.tm_mday; + sep_time.hour = ptm.tm_hour; + sep_time.minute = ptm.tm_min; + sep_time.second = ptm.tm_sec; + } +} + +time_t DateTime::FormatConvert(int year, int month, int day, int hour, int minute, int second) +{ + namespace ch = std::chrono; + try + { + ch::year_month_day ymd { ch::year{year}, ch::month{static_cast(month)}, ch::day{static_cast(day)} }; + if (!ymd.ok()) + return 0; + + auto local_tp = ch::local_days { ymd } + ch::hours { hour } + ch::minutes { minute } + ch::seconds { second }; + // 将本地时间根据当前系统时区转换为系统标准时间(UTC) + return ch::system_clock::to_time_t(ch::current_zone()->to_sys(local_tp)); + } + catch (...) + { + std::tm stm {}; + stm.tm_year = year - 1900; + stm.tm_mon = month - 1; + stm.tm_mday = day; + stm.tm_hour = hour; + stm.tm_min = minute; + stm.tm_sec = second; + stm.tm_isdst = -1; + return ::mktime(&stm); + } +} + +time_t DateTime::SpanedSeconds(Span sp) +{ + namespace ch = std::chrono; + auto total = ch::days { sp.days } + ch::hours { sp.hours } + ch::minutes { sp.minutes } + ch::seconds { sp.seconds }; + return ch::duration_cast(total).count(); +} + +DateTime::Span DateTime::ToSpan(time_t tim) +{ + // 修复了原代码中天数数额赋给秒、余数计算混乱的 Bug + namespace ch = std::chrono; + ch::seconds total_secs { tim }; + + auto d = ch::duration_cast(total_secs); + total_secs -= d; + + auto h = ch::duration_cast(total_secs); + total_secs -= h; + + auto m = ch::duration_cast(total_secs); + total_secs -= m; + + Span sp; + sp.days = static_cast(d.count()); + sp.hours = static_cast(h.count()); + sp.minutes = static_cast(m.count()); + sp.seconds = static_cast(total_secs.count()); + return sp; +} + +DateTime::DateTime() +{ + storage = ::time(nullptr); + UpdateDT(); +} + +DateTime::DateTime(time_t t) +{ + storage = t; + UpdateDT(); +} + +DateTime::DateTime(const tm& stm) +{ + storage = ::mktime(const_cast(&stm)); + UpdateDT(); +} + +DateTime::DateTime(const Full& ftm) +{ + storage = FormatConvert(ftm.year, ftm.month, ftm.day, ftm.hour, ftm.minute, ftm.second); + UpdateDT(); +} + +DateTime::DateTime(int year, int month, int day, int hour, int minute, int second) +{ + storage = FormatConvert(year, month, day, hour, minute, second); + UpdateDT(); +} + +DateTime::DateTime(const DateTime& obj) +{ + storage = obj.storage; + sep_time = obj.sep_time; +} + +DateTime DateTime::Now() +{ + return DateTime(::time(nullptr)); +} + +time_t DateTime::GetTimeStamp() const +{ + return storage; +} + +int DateTime::GetYear() const +{ + return sep_time.year; +} + +int DateTime::GetMonth() const +{ + return sep_time.month; +} + +int DateTime::GetDay() const +{ + return sep_time.day; +} + +int DateTime::GetHour() const +{ + return sep_time.hour; +} + +int DateTime::GetMinute() const +{ + return sep_time.minute; +} + +int DateTime::GetSecond() const +{ + return sep_time.second; +} + +DateTime::Full DateTime::GetFullDateTime() const +{ + return sep_time; +} + +std::string DateTime::Format(std::string fmt_str) +{ + namespace ch = std::chrono; + try + { + // 1. 获取系统标准时间点 + auto tp = ch::system_clock::from_time_t(storage); + + // 2. 转换为本地时间点 + auto local_tp = ch::current_zone()->to_local(tp); + + // 3. 截断到秒级精度(防止打印出纳秒/微秒的小数点),并获取 C++20 支持格式化的强类型 + auto local_secs = ch::floor(local_tp); + + // 4. 组装 C++20 chrono 格式化字符串并执行 + std::string chrono_fmt = "{:" + fmt_str + "}"; + return std::vformat(chrono_fmt, std::make_format_args(local_secs)); + } + catch (...) + { + // 如果 C++20 运行时时区数据库不可用,或者 fmt_str 包含不兼容的传统旧语法,平滑降级 + size_t size = (fmt_str.size() * 2) + 10; + auto recv = std::make_unique(size); + std::tm suctm = static_cast(*this); + size_t ret = ::strftime(recv.get(), size, fmt_str.c_str(), &suctm); + while (ret == 0) + { + size *= 2; + recv = std::make_unique(size); + ret = ::strftime(recv.get(), size, fmt_str.c_str(), &suctm); + } + return std::string(recv.get()); + } +} + +bool DateTime::IsAM() +{ + return (sep_time.hour < 12); +} // 修正逻辑:通常12点后为PM + +bool DateTime::IsPM() +{ + return (sep_time.hour >= 12); +} + +bool DateTime::TimePassed() +{ + return (storage < ::time(nullptr)); +} // 修正逻辑:过去的时间应该小于现在 + +DateTime::operator tm() +{ + std::tm stm {}; + stm.tm_year = sep_time.year - 1900; + stm.tm_mon = sep_time.month - 1; + stm.tm_mday = sep_time.day; + stm.tm_hour = sep_time.hour; + stm.tm_min = sep_time.minute; + stm.tm_sec = sep_time.second; + stm.tm_isdst = -1; + return stm; +} + +DateTime::operator Full() +{ + return sep_time; +} + +DateTime::operator time_t() +{ + return storage; +} + +DateTime::operator std::string() +{ + return Format("%Y-%m-%dT%H:%M:%S+08:00"); +} + +// ================== 运算符重载实现 ================== +DateTime::Span DateTime::operator-(const tm& stm) +{ + return ToSpan(storage - ::mktime(const_cast(&stm))); +} + +DateTime::Span DateTime::operator-(const Span& ts) +{ + return ToSpan(storage - SpanedSeconds(ts)); +} + +DateTime::Span DateTime::operator-(const Full& sdt) +{ + return ToSpan(storage - FormatConvert(sdt.year, sdt.month, sdt.day, sdt.hour, sdt.minute, sdt.second)); +} + +DateTime::Span DateTime::operator-(const time_t& t) +{ + return ToSpan(storage - t); +} + +DateTime::Span DateTime::operator-(const DateTime& dt) +{ + return ToSpan(storage - dt.storage); +} + +DateTime::Span DateTime::operator+(const tm& stm) +{ + return ToSpan(storage + ::mktime(const_cast(&stm))); +} + +DateTime::Span DateTime::operator+(const Span& ts) +{ + return ToSpan(storage + SpanedSeconds(ts)); +} + +DateTime::Span DateTime::operator+(const Full& sdt) +{ + return ToSpan(storage + FormatConvert(sdt.year, sdt.month, sdt.day, sdt.hour, sdt.minute, sdt.second)); +} + +DateTime::Span DateTime::operator+(const time_t& t) +{ + return ToSpan(storage + t); +} + +DateTime::Span DateTime::operator+(const DateTime& dt) +{ + return ToSpan(storage + dt.storage); +} + +DateTime DateTime::operator=(const tm& stm) +{ + storage = ::mktime(const_cast(&stm)); UpdateDT(); return *this; +} + +DateTime DateTime::operator=(const Full& sdt) +{ + storage = FormatConvert(sdt.year, sdt.month, sdt.day, sdt.hour, sdt.minute, sdt.second); UpdateDT(); return *this; +} + +DateTime DateTime::operator=(const time_t& t) +{ + storage = t; UpdateDT(); return *this; +} + +DateTime DateTime::operator=(const DateTime& dt) +{ + storage = dt.storage; UpdateDT(); return *this; +} + +DateTime DateTime::operator+=(const tm& stm) +{ + storage += ::mktime(const_cast(&stm)); UpdateDT(); return *this; +} + +DateTime DateTime::operator+=(const Span& ts) +{ + storage += SpanedSeconds(ts); UpdateDT(); return *this; +} + +DateTime DateTime::operator+=(const Full& sdt) +{ + storage += FormatConvert(sdt.year, sdt.month, sdt.day, sdt.hour, sdt.minute, sdt.second); UpdateDT(); return *this; +} + +DateTime DateTime::operator+=(const time_t& t) +{ + storage += t; UpdateDT(); return *this; +} + +DateTime DateTime::operator+=(const DateTime& dt) +{ + storage += dt.storage; UpdateDT(); return *this; +} + +DateTime DateTime::operator-=(const tm& stm) +{ + storage -= ::mktime(const_cast(&stm)); UpdateDT(); return *this; +} + +DateTime DateTime::operator-=(const Span& ts) +{ + storage -= SpanedSeconds(ts); UpdateDT(); return *this; +} + +DateTime DateTime::operator-=(const Full& sdt) +{ + storage -= FormatConvert(sdt.year, sdt.month, sdt.day, sdt.hour, sdt.minute, sdt.second); UpdateDT(); return *this; +} + +DateTime DateTime::operator-=(const time_t& t) +{ + storage -= t; UpdateDT(); return *this; +} + +DateTime DateTime::operator-=(const DateTime& dt) +{ + storage -= dt.storage; UpdateDT(); return *this; +} + +bool DateTime::operator==(const tm& stm) +{ + return storage == ::mktime(const_cast(&stm)); +} + +bool DateTime::operator==(const Full& sdt) +{ + return storage == FormatConvert(sdt.year, sdt.month, sdt.day, sdt.hour, sdt.minute, sdt.second); +} + +bool DateTime::operator==(const time_t& t) +{ + return storage == t; +} + +bool DateTime::operator==(const DateTime& dt) +{ + return storage == dt.storage; +} + +bool DateTime::operator!=(const tm& stm) +{ + return !(*this == stm); +} + +bool DateTime::operator!=(const Full& sdt) +{ + return !(*this == sdt); +} + +bool DateTime::operator!=(const time_t& t) +{ + return storage != t; +} + +bool DateTime::operator!=(const DateTime& dt) +{ + return storage != dt.storage; +} + +bool DateTime::operator>(const tm& stm) +{ + return storage > ::mktime(const_cast(&stm)); +} + +bool DateTime::operator>(const Full& sdt) +{ + return storage > FormatConvert(sdt.year, sdt.month, sdt.day, sdt.hour, sdt.minute, sdt.second); +} + +bool DateTime::operator>(const time_t& t) +{ + return storage > t; +} + +bool DateTime::operator>(const DateTime& dt) +{ + return storage > dt.storage; +} + +bool DateTime::operator>=(const tm& stm) +{ + return storage >= ::mktime(const_cast(&stm)); +} + +bool DateTime::operator>=(const Full& sdt) +{ + return storage >= FormatConvert(sdt.year, sdt.month, sdt.day, sdt.hour, sdt.minute, sdt.second); +} + +bool DateTime::operator>=(const time_t& t) +{ + return storage >= t; +} + +bool DateTime::operator>=(const DateTime& dt) +{ + return storage >= dt.storage; +} + +bool DateTime::operator<(const tm& stm) +{ + return storage < ::mktime(const_cast(&stm)); +} + +bool DateTime::operator<(const Full& sdt) +{ + return storage < FormatConvert(sdt.year, sdt.month, sdt.day, sdt.hour, sdt.minute, sdt.second); +} + +bool DateTime::operator<(const time_t& t) +{ + return storage < t; +} + +bool DateTime::operator<(const DateTime& dt) +{ + return storage < dt.storage; +} + +bool DateTime::operator<(const DateTime& dt) const +{ + return storage < dt.storage; +} + +bool DateTime::operator<=(const tm& stm) +{ + return storage <= ::mktime(const_cast(&stm)); +} + +bool DateTime::operator<=(const Full& sdt) +{ + return storage <= FormatConvert(sdt.year, sdt.month, sdt.day, sdt.hour, sdt.minute, sdt.second); +} + +bool DateTime::operator<=(const time_t& t) +{ + return storage <= t; +} + +bool DateTime::operator<=(const DateTime& dt) +{ + return storage <= dt.storage; +} \ No newline at end of file diff --git a/UNSWebServerCore/DateTime.h b/UNSWebServerCore/DateTime.h new file mode 100644 index 0000000..55d946c --- /dev/null +++ b/UNSWebServerCore/DateTime.h @@ -0,0 +1,122 @@ +#pragma once +#include +#include +#include "Export.h" + +class UNSWSC_DLL_EXPORT DateTime +{ +public: + struct UNSWSC_DLL_EXPORT Full + { + int year, month, day, hour, minute, second; + }; + + struct UNSWSC_DLL_EXPORT Span + { + int days, hours, minutes, seconds; + }; + +private: + time_t storage; + Full sep_time; + +private: + // 移除了硬编码的秒数常量,内部改用 std::chrono 处理 + void UpdateDT(); + time_t FormatConvert(int year, int month, int day, int hour, int minute, int second); + time_t SpanedSeconds(Span sp); + Span ToSpan(time_t tim); + +public: + DateTime(); + DateTime(time_t t); + DateTime(const tm& stm); + DateTime(const Full& ftm); + DateTime(const DateTime& obj); + DateTime(int year, int month, int day, int hour = 0, int minute = 0, int second = 0); + +public: + static DateTime Now(); + +public: + int GetYear() const; + int GetMonth() const; + int GetDay() const; + int GetHour() const; + int GetMinute() const; + int GetSecond() const; + time_t GetTimeStamp() const; + Full GetFullDateTime() const; + std::string Format(std::string fmt_str); + +public: + bool IsAM(); + bool IsPM(); + bool TimePassed(); + +public: + operator tm(); + operator Full(); + operator time_t(); + operator std::string(); + + Span operator-(const tm& stm); + Span operator-(const Span& ts); + Span operator-(const Full& sdt); + Span operator-(const time_t& t); + Span operator-(const DateTime& dt); + + Span operator+(const tm& stm); + Span operator+(const Span& ts); + Span operator+(const Full& sdt); + Span operator+(const time_t& t); + Span operator+(const DateTime& dt); + + DateTime operator=(const tm& stm); + DateTime operator=(const Full& sdt); + DateTime operator=(const time_t& t); + DateTime operator=(const DateTime& dt); + + DateTime operator+=(const tm& stm); + DateTime operator+=(const Span& ts); + DateTime operator+=(const Full& sdt); + DateTime operator+=(const time_t& t); + DateTime operator+=(const DateTime& dt); + + DateTime operator-=(const tm& stm); + DateTime operator-=(const Span& ts); + DateTime operator-=(const Full& sdt); + DateTime operator-=(const time_t& t); + DateTime operator-=(const DateTime& dt); + + bool operator==(const tm& stm); + bool operator==(const Full& sdt); + bool operator==(const time_t& t); + bool operator==(const DateTime& dt); + + bool operator!=(const tm& stm); + bool operator!=(const Full& sdt); + bool operator!=(const time_t& t); + bool operator!=(const DateTime& dt); + + bool operator>(const tm& stm); + bool operator>(const Full& sdt); + bool operator>(const time_t& t); + bool operator>(const DateTime& dt); + + bool operator>=(const tm& stm); + bool operator>=(const Full& sdt); + bool operator>=(const time_t& t); + bool operator>=(const DateTime& dt); + + bool operator<(const tm& stm); + bool operator<(const Full& sdt); + bool operator<(const time_t& t); + bool operator<(const DateTime& dt); + bool operator<(const DateTime& dt) const; + + bool operator<=(const tm& stm); + bool operator<=(const Full& sdt); + bool operator<=(const time_t& t); + bool operator<=(const DateTime& dt); +}; \ No newline at end of file diff --git a/UNSWebServerCore/Export.h b/UNSWebServerCore/Export.h new file mode 100644 index 0000000..cf9e4f2 --- /dev/null +++ b/UNSWebServerCore/Export.h @@ -0,0 +1,15 @@ +#ifdef _WINDOWS + #ifdef UNSWEBSERVERCORE_EXPORTS + #define UNSWSC_DLL_EXPORT __declspec(dllexport) + #else + #define UNSWSC_DLL_EXPORT __declspec(dllimport) + #ifdef _DEBUG + #pragma comment(lib, "UNSWebServerCored.lib") + #else + #pragma comment(lib, "UNSWebServerCore.lib") + #endif + #endif + #pragma warning(disable: 4251) +#else + #define UNSWSC_DLL_EXPORT __attribute__((visibility("default"))) +#endif \ No newline at end of file diff --git a/UNSWebServerCore/FileReceiver.cpp b/UNSWebServerCore/FileReceiver.cpp new file mode 100644 index 0000000..062c837 --- /dev/null +++ b/UNSWebServerCore/FileReceiver.cpp @@ -0,0 +1,257 @@ +#include "FileReceiver.h" +#include +#include +#include "ServerLogger.h" +#include "PathTraversal.h" +#include "HTTPObjectsBridge.h" +#include "UNSResponseBuilder.h" + +class FileReceiver::Impl +{ +public: + bool EnableCORS = false; + bool HTMLResponse = false; + std::string TempRoot; + IPTablePtr BlockedIPs = nullptr; + WebFileInfoVec FileInfo; + FileProcessorCallback Callback = nullptr; +}; + +FileReceiver::FileReceiver() : pimpl(std::make_unique()) +{ +} + +FileReceiver::~FileReceiver() = default; + +bool FileReceiver::CallFileProcesser() +{ + if (pimpl->Callback == nullptr) + return false; + std::thread thFileProcesser(pimpl->Callback, pimpl->FileInfo, pimpl->TempRoot); //Use copy construst to avoid repeat data process. + if (!thFileProcesser.joinable()) + return false; + thFileProcesser.detach(); + pimpl->FileInfo.clear(); + SCLOGF_TRACE("FileProcesser Function (Address: {}) Started.", pimpl->Callback); + return true; +} + +void FileReceiver::SetResponseMode(bool html) +{ + pimpl->HTMLResponse = html; + SCLOG_DEBUG("FileReceiver init mode: %s", (html ? "html" : "json")); +} + +void FileReceiver::SetCORSEnable(bool enable) +{ + pimpl->EnableCORS = enable; + SCLOG_DEBUG("FileReceiver CORS mode: %s", (enable ? "enabled" : "disabled")); +} + +void FileReceiver::SetTempRoot(std::string temp_root) +{ + pimpl->TempRoot = temp_root; + SCLOG_TRACE("FR-TempRoot: %s", pimpl->TempRoot.c_str()); + return; +} + +void FileReceiver::SetFileCallback(FileProcessorCallback fpcb) +{ + pimpl->Callback = fpcb; + return; +} + +void FileReceiver::UpdateBlockedIPs(IPTablePtr ip) +{ + pimpl->BlockedIPs = ip; + return; +} + +void FileReceiver::AppenedBlockedIP(DateTime::Span block_time, std::string ip) +{ + DateTime expr_time = (DateTime::Now() += block_time); + IPList li{ ip }; + pimpl->BlockedIPs->Appened(expr_time, li); + pimpl->BlockedIPs->Update(); + SCLOG_INFO("IP: [%s] has been blocked untill {%s}", ip.c_str(), std::string(expr_time).c_str()); + return; +} + +uns::HTTPMethod FileReceiver::GetMethod(uns::RequestPtr request) +{ + std::string method = request->GetImpl()->webcc_req->method(); + if (method == "GET") + return uns::HTTPMethod::H_GET; + else if (method == "PUT") + return uns::HTTPMethod::H_PUT; + else if (method == "POST") + return uns::HTTPMethod::H_POST; + else if (method == "HEAD") + return uns::HTTPMethod::H_HEAD; + else if (method == "TRACE") + return uns::HTTPMethod::H_TRACE; + else if (method == "PATCH") + return uns::HTTPMethod::H_PATCH; + else if (method == "DELETE") + return uns::HTTPMethod::H_DELETE; + else if (method == "OPTIONS") + return uns::HTTPMethod::H_OPTIONS; + else if (method == "CONNECT") + return uns::HTTPMethod::H_CONNECT; + else + return uns::HTTPMethod::H_UNKNOWN; +} + +bool FileReceiver::WriteFile(const std::string& path, const std::string& bytes) +{ + //Code from WebCC source (commit 554470ac65f9fa08b53ee9adeb819ae7c70ef698). + std::ofstream stream{ path, std::ios::binary }; + if (stream.fail()) + { + SCLOG_WARNING("Failed to write file [%s]: can't open stream", path.c_str()); + return false; + } + stream.write(bytes.data(), bytes.size()); + if (stream.fail()) + SCLOG_WARNING("Failed to write file [%s]: can't write to stream", path.c_str()); + return !stream.fail(); +} + +uns::PathTraversalDefenceLevel FileReceiver::PTDefence() +{ + return uns::PathTraversalDefenceLevel::DenyAll; +} + +bool FileReceiver::IsPathSafe(const std::string& raw_path) +{ + // 无外部重载时认为所有路径均不能通过检查 + return false; +} + +std::string FileReceiver::EncodeUploadResult() +{ + Json::Value root; + Json::FastWriter writer; + root["AcceptedCount"] = pimpl->FileInfo.size(); + root["AcceptedFiles"] = Json::Value(Json::arrayValue); + for (auto& ele : pimpl->FileInfo) + { + Json::Value sub; + sub["FileName"] = ele.GetStorageFileName(); + sub["UploadTime"] = ele.GetUploadTime().GetTimeStamp(); + root["AcceptedFiles"].append(sub); + } + return writer.write(root); +} + +std::string FileReceiver::EncodeUploadResultHTML() +{ + const char* html = R"( + + + + Upload Result + + +
+

Upload Result

+
+

AcceptedCount: %lld

+

AcceptedFiles:
%s

+
+ + +)"; + std::string tmp; + for (auto& ele : pimpl->FileInfo) + tmp += "[" + ele.GetStorageFileName() + "] - {" + ele.GetUploadTime().Format("%Y-%m-%d %H:%M:%S") + "}
"; + size_t html_size = strlen(html) + tmp.size() + 10; + char* result = new char[html_size]; + memset(result, 0, sizeof(result)); + sprintf(result, html, pimpl->FileInfo.size(), tmp.c_str()); + tmp = std::string(result); + delete[] result; + return tmp; +} + +uns::ResponsePtr FileReceiver::Execute(uns::RequestPtr request) +{ + uns::HTTPMethod method = GetMethod(request); + std::string x_real_ip; + if(request->GetImpl()->webcc_req->HasHeader("X-Real-IP")) + x_real_ip = request->GetImpl()->webcc_req->GetHeader("X-Real-IP"); + std::string req_ip = (x_real_ip.empty() ? request->GetImpl()->webcc_req->address() : x_real_ip); + SCLOG_DEBUG("Request recived, ip: [%s], method: %s", req_ip.c_str(), request->GetImpl()->webcc_req->method().c_str()); + // path test + std::string path = request->GetImpl()->webcc_req->url().path(); + auto status = PathTraversal::AnalyzeUrlTraversal(path); + if(status != PathTraversal::UrlSafetyStatus::Safe) + SCLOGF_WARNING("PathTraversal Detected: {}, Level: {}", path, PathTraversal::ToString(status)); + switch(PTDefence()) + { + case uns::PathTraversalDefenceLevel::DenyAll: + if(status != PathTraversal::UrlSafetyStatus::Safe) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()()); + break; + case uns::PathTraversalDefenceLevel::AutoNormalize: + { + if(status == PathTraversal::UrlSafetyStatus::EvasiveTraversal) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()()); + auto decoded_path = PathTraversal::UrlDecode(path); + if(!IsPathSafe(decoded_path)) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()()); + auto url = request->GetImpl()->webcc_req->url(); + url.ForceSet_Path(PathTraversal::NormalizeUrlPath(decoded_path)); + request->GetImpl()->webcc_req->set_url(std::move(url)); + break; + } + case uns::PathTraversalDefenceLevel::AllowNormal: + { + if(status == PathTraversal::UrlSafetyStatus::EvasiveTraversal) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()()); + auto decoded_path = PathTraversal::UrlDecode(path); + if(!IsPathSafe(decoded_path)) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()()); + auto url = request->GetImpl()->webcc_req->url(); + url.ForceSet_Path(decoded_path); + request->GetImpl()->webcc_req->set_url(std::move(url)); + break; + } + default: + break; //Check Bypassed by [AllowAll] + } + if ((method & uns::H_PUT) || (method & uns::H_POST)) + { + if (pimpl->BlockedIPs != nullptr) + { + std::string req_ip = request->GetImpl()->webcc_req->address(); + pimpl->BlockedIPs->Update(); + if (pimpl->BlockedIPs->IPExist(req_ip)) + return (pimpl->EnableCORS ? uns::ResponseBuilder().IPBlocked().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().IPBlocked().EmptyBody()()); + } + webcc::Status tmpStatus = uns::ConvertStatus(PreCheckRequest(request)); + if (tmpStatus != webcc::kOK) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Code(tmpStatus).EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Code(tmpStatus).EmptyBody()()); + else if (!request->GetImpl()->webcc_req->IsForm()) + return (pimpl->EnableCORS ? uns::ResponseBuilder().RequestFormatError().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().RequestFormatError().EmptyBody()()); + else + { + for (auto& form : request->GetFormParts()) + { + if (form->GetFileName().empty()) + continue; + if (!PreCheckForm(form)) + continue; + SCLOGF_DEBUG("File recived: [{}], {} bytes", form->GetFileName(), form->GetDataSize()); + WebFileInfo info(form->GetFileNameS(), form->GetDataSize()); + WriteFile(info.MakePath(pimpl->TempRoot), form->GetData()); + pimpl->FileInfo.push_back(info); + } + std::string resp_body = (pimpl->HTMLResponse ? EncodeUploadResultHTML() : EncodeUploadResult()); + CallFileProcesser(); + return (pimpl->EnableCORS ? uns::ResponseBuilder().Created().Body(resp_body).AutoCORS(request)() : uns::ResponseBuilder().Created().Body(resp_body)()); + } + } + else + return (pimpl->EnableCORS ? uns::ResponseBuilder().IllegalUpload().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().IllegalUpload().EmptyBody()()); +} diff --git a/UNSWebServerCore/FileReceiver.h b/UNSWebServerCore/FileReceiver.h new file mode 100644 index 0000000..975fa6b --- /dev/null +++ b/UNSWebServerCore/FileReceiver.h @@ -0,0 +1,44 @@ +#pragma once +#include "Global.h" +#include "IPTable.h" +#include +#include "WebFileInfo.h" +#include "HTTPObjects.h" + +using FileProcessorCallback = std::function; + +class UNSWSC_DLL_EXPORT FileReceiver +{ +protected: + class Impl; + std::unique_ptr pimpl; + +public: + FileReceiver(); + virtual ~FileReceiver(); // 基类必须有虚析构函数! + +public: + bool CallFileProcesser(); + void SetResponseMode(bool html); + void SetCORSEnable(bool enable); + std::string EncodeUploadResult(); + std::string EncodeUploadResultHTML(); + void UpdateBlockedIPs(IPTablePtr ip); + void SetTempRoot(std::string temp_root); + void SetFileCallback(FileProcessorCallback fpcb); + 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); + +public: + virtual uns::Status PreCheckRequest(uns::RequestPtr request) = 0; + virtual bool PreCheckForm(uns::FormPartPtr form) = 0; + virtual uns::PathTraversalDefenceLevel PTDefence(); + virtual bool IsPathSafe(const std::string& raw_path); + +public: + // 核心驱动入口:供内部适配器调用的实际执行流 + uns::ResponsePtr Execute(uns::RequestPtr request); +}; + +using FileReceiverPtr = std::shared_ptr; \ No newline at end of file diff --git a/UNSWebServerCore/Global.cpp b/UNSWebServerCore/Global.cpp new file mode 100644 index 0000000..764cbcd --- /dev/null +++ b/UNSWebServerCore/Global.cpp @@ -0,0 +1,211 @@ +#include "Global.h" +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include "UOHash.h" +#else +#include "../UOHash/UOHash.h" +#endif +#include +#include "SafeRNG.h" +#include +#include +#include "PathTraversal.h" + +std::string uns::EncodeErrorPage(int code) +{ + // size_t str_size = strlen(G_SERVER_NAME) + strlen(G_ERROR_PAGE); + // char* buffer = new char[str_size]; + // memset(buffer, 0, str_size); + // snprintf(buffer, str_size, G_ERROR_PAGE, code, code, G_SERVER_NAME); + // std::string ret = buffer; + // delete[] buffer; + // return ret; + return std::vformat(G_ERROR_PAGE, std::make_format_args(code, code, G_SERVER_NAME)); +} + +std::string uns::EncodeHTTPTime(time_t* time) +{ + // struct tm t; + // if (time != NULL) + // gmtime_r(time, &t); + // else + // { + // int64_t ltime_cur; + // ::time(<ime_cur); + // gmtime_r(<ime_cur, &t); + // } + // char szTime[100] = { 0 }; + // // - Sun, 24 Aug 2008 22:43:45 GMT + // sprintf(szTime, "%s, %d %s %d %d:%d:%d GMT", G_HTTP_STD_WEEK[t.tm_wday].c_str(), t.tm_mday, G_HTTP_STD_MONTH[t.tm_mon].c_str(), t.tm_year + 1900, t.tm_hour, t.tm_min, t.tm_sec); + // return szTime; + // 1. 一行搞定时间点获取:如果指针有效就转换,否则直接取当前系统时间 + auto tp = time ? std::chrono::system_clock::from_time_t(*time) : std::chrono::system_clock::now(); + // 2. 强制截断到“秒”级(floor),避免有些平台输出微秒等尾巴 + auto tp_secs = std::chrono::floor(tp); + // 3. 直接利用 chrono 占位符格式化输出 + // %a: 星期缩写(Sun), %d: 两位日子(02), %b: 月份缩写(Jun), %Y: 四位年份(2026), %T: 24小时制时间(00:00:00) + return std::format("{:%a, %d %b %Y %T} GMT", tp_secs); +} + +std::string uns::btos(bool val) +{ + return (val ? "true" : "false"); +} + +bool uns::stob(const std::string& obj) +{ + return (obj == "true"); +} + +DateTime uns::stot(const std::string& obj) +{ + int nYear, nMonth, nDate, nHour, nMin, nSec; + int cnt = sscanf(obj.c_str(), "%d-%d-%d %d:%d:%d", &nYear, &nMonth, &nDate, &nHour, &nMin, &nSec); + if (cnt != 6) + return DateTime(); + else + return DateTime(nYear, nMonth, nDate, nHour, nMin, nSec); +} + +std::string uns::RestoreURL(const std::string& url) +{ + std::string res = url; + std::replace(res.begin(), res.end(), '{', '&'); + std::replace(res.begin(), res.end(), '}', '='); + return res; +} + +void uns::Stringsplit(const std::string& str, const std::string& splits, std::vector& res) +{ + if (str == "") + return; + std::string strs = str + splits; + size_t pos = strs.find(splits); + size_t step = splits.size(); + while (pos != strs.npos) + { + std::string temp = strs.substr(0, pos); + res.push_back(temp); + strs = strs.substr(pos + step, strs.size()); + pos = strs.find(splits); + } +} + +void uns::ProcessPOSTArgs(const std::string& args, POSTArgs& args_output) +{ + args_output.clear(); + std::vector c1_split, c2_split; + Stringsplit(args, "&", c1_split); + for (const auto& kv : c1_split) + { + c2_split.clear(); + c2_split.shrink_to_fit(); + Stringsplit(kv, "=", c2_split); + if (c2_split.size() != 2) + continue; + if (c2_split[0] == "origin_url") + args_output.insert({ c2_split[0], RestoreURL(c2_split[1]) }); + else + args_output.insert({ c2_split[0], c2_split[1] }); + } +} + +std::string uns::tools::EncryptPassword(const std::string reg_date, const std::string& password) +{ + std::string res_reg_date = reg_date; + std::reverse(res_reg_date.begin(), res_reg_date.end()); + std::string war_pwd = reg_date + password + res_reg_date; + auto res = uns::UOHash::HashString(uns::HashID::SHA3_512, war_pwd); + if (!res) + return ""; + return res.GetResult(); +} + +std::string uns::tools::EncryptPasswordSodium(const std::string& pwd) +{ + // ======== 参数(128MB 内存) ======== + static const size_t MEMLIMIT = 128UL * 1024 * 1024; // 128 MB + static const uint64_t OPSLIMIT = crypto_pwhash_OPSLIMIT_MODERATE; + // ===================================== + + // 输出缓冲:libsodium 定义的固定大小 + char out_str[crypto_pwhash_STRBYTES] = { 0 }; + // 生成编码字符串(包含算法、参数、盐、hash),直接存 DB(TEXT) + if (crypto_pwhash_str(out_str, pwd.c_str(), pwd.size(), OPSLIMIT, MEMLIMIT) != 0) + return ""; + else + return std::string(out_str); +} + +bool uns::tools::CheckPasswordSodium(const std::string& pwd, const std::string& encrypted) +{ + return (crypto_pwhash_str_verify(encrypted.c_str(), pwd.c_str(), pwd.length()) == 0); +} + +std::string uns::tools::SecureRandomHex(size_t bytes) +{ + return RandomNumberGenerator::SecureRandomHex(bytes); +} + +std::string uns::tools::ToUpper(const std::string& s) +{ + std::string r; r.reserve(s.size()); + for (unsigned char c : s) + r.push_back(static_cast(std::toupper(c))); + return r; +} + +std::string uns::tools::ToLower(const std::string& s) +{ + std::string r; r.reserve(s.size()); + for (unsigned char c : s) + r.push_back(static_cast(std::tolower(c))); + return r; +} + +std::string uns::tools::CalculateFileHashSHA256(const std::string & file) +{ + // 1. 以二进制模式打开文件 + std::ifstream ifs(file, std::ios::binary); + if (!ifs.is_open()) // 文件打开失败,返回空字符串(也可以根据你的项目规范记录日志或抛出异常) + return std::string(); + // 2. 初始化 OpenSSL EVP 上下文,使用智能指针自动管理内存释放 + std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); + if (!ctx) + return std::string(); + // 3. 指定使用 SHA256 算法 + if (EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr) != 1) + return std::string(); + // 4. 分块读取文件并更新哈希计算(每次读取 4096 字节,避免大文件占用过多内存) + constexpr size_t kBufferSize = 4096; + char buffer[kBufferSize]; + while (ifs.read(buffer, kBufferSize) || ifs.gcount() > 0) + { + if (EVP_DigestUpdate(ctx.get(), buffer, ifs.gcount()) != 1) + return std::string(); + } + // 5. 结束哈希计算并获取二进制结果 + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int length = 0; + if (EVP_DigestFinal_ex(ctx.get(), hash, &length) != 1) + return std::string(); + // 6. 将二进制哈希值转换为 16 进制字符串 (Hex String) + std::string hex_result; + hex_result.reserve(length * 2); + for (unsigned int i = 0; i < length; ++i) + { + char buf[3]; + snprintf(buf, sizeof(buf), "%02x", hash[i]); + hex_result.append(buf); + } + return hex_result; +} + +bool uns::secure::IsSafePath(const std::string & safe_path, const std::string & requested_path) +{ + return PathTraversal::IsSafePath(safe_path, requested_path); +} diff --git a/UNSWebServerCore/Global.h b/UNSWebServerCore/Global.h new file mode 100644 index 0000000..df360fc --- /dev/null +++ b/UNSWebServerCore/Global.h @@ -0,0 +1,155 @@ +#pragma once +#include +#include +#include +#include "Export.h" +#include "DateTime.h" + +#pragma warning(disable : 4455) + +#define G_SERVER_VERSION "2.0.0" + +constexpr double G_SERVICE_UPDATE_TIMESPAN = 1000; + +#define G_SERVER_NAME "U.N.S. Server Core/" G_SERVER_VERSION + +constexpr auto G_SERVICE_NAME = "UNS_HTTP_ServerCore"; + +constexpr auto G_ERROR_PAGE = R"( + + + + HTTP {:03d} + + +
+

HTTP ERROR {:03d}

+
+

{}

+
+ + +)"; + +// inline constexpr std::string_view G_HTTP_STD_MONTH[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +// inline constexpr std::string_view G_HTTP_STD_WEEK[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + +namespace uns +{ + enum HTTPMethod + { + H_GET = 0b0000000001, + H_PUT = 0b0000000010, + H_POST = 0b0000000100, + H_HEAD = 0b0000001000, + H_TRACE = 0b0000010000, + H_PATCH = 0b0000100000, + H_DELETE = 0b0001000000, + H_OPTIONS = 0b0010000000, + H_CONNECT = 0b0100000000, + H_UNKNOWN = 0b1000000000, + H_ALL_ENABLED = 0b1111111111 + }; + + namespace cors + { + inline constexpr std::string_view reqh_o = "Origin"; + inline constexpr std::string_view reqh_acrm = "Access-Control-Request-Method"; + inline constexpr std::string_view reqh_acrh = "Access-Control-Request-Headers"; + + inline constexpr std::string_view resh_acao = "Access-Control-Allow-Origin"; + inline constexpr std::string_view resh_acam = "Access-Control-Allow-Methods"; + inline constexpr std::string_view resh_acah = "Access-Control-Allow-Headers"; + inline constexpr std::string_view resh_acac = "Access-Control-Allow-Credentials"; + inline constexpr std::string_view resh_acma = "Access-Control-Max-Age"; + }; + + using POSTArgs = std::map; + + std::string UNSWSC_DLL_EXPORT EncodeErrorPage(int code); + std::string UNSWSC_DLL_EXPORT EncodeHTTPTime(time_t* time); + + std::string UNSWSC_DLL_EXPORT btos(bool val); + bool UNSWSC_DLL_EXPORT stob(const std::string& obj); + DateTime UNSWSC_DLL_EXPORT stot(const std::string& obj); + std::string UNSWSC_DLL_EXPORT RestoreURL(const std::string& url); + void UNSWSC_DLL_EXPORT Stringsplit(const std::string& str, const std::string& splits, std::vector& res); + void UNSWSC_DLL_EXPORT ProcessPOSTArgs(const std::string& args, POSTArgs& args_output); + + namespace tools + { + //基于SHA3-256,无暴力破解防护能力,不建议使用 + std::string UNSWSC_DLL_EXPORT EncryptPassword(const std::string reg_date, const std::string& password); + //安全的密码加密函数,基于libsodium + std::string UNSWSC_DLL_EXPORT EncryptPasswordSodium(const std::string& pwd); + //密码检查函数 + bool UNSWSC_DLL_EXPORT CheckPasswordSodium(const std::string& pwd, const std::string& encrypted); + + //安全随机数 + std::string UNSWSC_DLL_EXPORT SecureRandomHex(size_t bytes); + + std::string UNSWSC_DLL_EXPORT ToUpper(const std::string& s); + std::string UNSWSC_DLL_EXPORT ToLower(const std::string& s); + + std::string UNSWSC_DLL_EXPORT CalculateFileHashSHA256(const std::string& file); + } + + namespace secure + { + /** + * @brief 基于物理与逻辑边界的路径穿透(目录穿越)安全校验 + * @param safe_path 允许访问的沙盒根目录(绝对或相对路径均可) + * @param requested_path 客户端传入的、解码后的目标子路径 + * @return true 安全(在沙盒内);false 不安全(企图穿越或路径非法) + */ + bool IsSafePath(const std::string& safe_path, const std::string& requested_path); + } +}; + +#undef MIN + +inline constexpr unsigned long long operator""B(unsigned long long n) +{ + return n; +} + +inline constexpr unsigned long long operator""KB(unsigned long long n) +{ + return (n * 1024); +} + +inline constexpr unsigned long long operator""MB(unsigned long long n) +{ + return (n * 1024 * 1024); +} + +inline constexpr unsigned long long operator""GB(unsigned long long n) +{ + return (n * 1024 * 1024 * 1024); +} + +inline constexpr unsigned long long operator""TB(unsigned long long n) +{ + return (n * 1024 * 1024 * 1024 * 1024); +} + +inline constexpr time_t operator""S(unsigned long long t) +{ + return t; +} + +inline constexpr time_t operator""MIN(unsigned long long t) +{ + return (t * 60); +} + +inline constexpr time_t operator""HOUR(unsigned long long t) +{ + return (t * 60 * 60); +} + +inline constexpr time_t operator""DAY(unsigned long long t) +{ + return (t * 60 * 60 * 24); +} diff --git a/UNSWebServerCore/HTTPObjects.cpp b/UNSWebServerCore/HTTPObjects.cpp new file mode 100644 index 0000000..b5a688c --- /dev/null +++ b/UNSWebServerCore/HTTPObjects.cpp @@ -0,0 +1,283 @@ +#include "HTTPObjects.h" +#include "HTTPObjectsBridge.h" + +namespace uns +{ + UrlQuery::UrlQuery() : pimpl(std::make_unique(webcc::UrlQuery{ "" })) + { + } + + UrlQuery::UrlQuery(std::unique_ptr impl) : pimpl(std::move(impl)) + { + } + + UrlQuery::~UrlQuery() = default; + + UrlQuery::UrlQuery(const UrlQuery& other) + : pimpl(std::make_unique(other.pimpl->webcc_query)) + { + } + + UrlQuery& UrlQuery::operator=(const UrlQuery& other) + { + if (this != &other) + { + pimpl = std::make_unique(other.pimpl->webcc_query); + } + return *this; + } + + UrlQuery::UrlQuery(UrlQuery&& other) noexcept = default; + UrlQuery& UrlQuery::operator=(UrlQuery&& other) noexcept = default; + + bool UrlQuery::Empty() const + { + return pimpl->webcc_query.Empty(); + } + + std::size_t UrlQuery::Size() const + { + return pimpl->webcc_query.Size(); + } + + bool UrlQuery::Has(const std::string& key) const + { + return pimpl->webcc_query.Has(key); + } + + const std::string& UrlQuery::Get(const std::string& key) const + { + return pimpl->webcc_query.Get(key); + } + + std::pair UrlQuery::Get(std::size_t index) const + { + const auto& raw_param = pimpl->webcc_query.Get(index); + return { raw_param.first, raw_param.second }; + } + + std::string UrlQuery::ToString(bool encode) const + { + return pimpl->webcc_query.ToString(encode); + } +} + +uns::FormPart::FormPart(std::unique_ptr impl) : pimpl(std::move(impl)) +{ +} + +uns::FormPart::~FormPart() = default; + +std::string uns::FormPart::GetNameS() const +{ + return pimpl->webcc_form->name(); +} + +std::string_view uns::FormPart::GetName() const +{ + return pimpl->webcc_form->name(); +} + +std::string uns::FormPart::GetFileNameS() const +{ + return pimpl->webcc_form->file_name(); +} + +std::string_view uns::FormPart::GetFileName() const +{ + return pimpl->webcc_form->file_name(); +} + +std::string uns::FormPart::GetMediaTypeS() const +{ + return pimpl->webcc_form->media_type(); +} + +std::string_view uns::FormPart::GetMediaType() const +{ + return pimpl->webcc_form->media_type(); +} + +const std::string& uns::FormPart::GetData() const +{ + return pimpl->webcc_form->data(); +} + +std::size_t uns::FormPart::GetSize() const +{ + // 注意:虽然 webcc 原生的 GetSize() 没有写 const 修饰符, + // 但由于 pimpl 内部存的是智能指针,这里依然可以在 const 函数里安全调用它 + return pimpl->webcc_form->GetSize(); +} + +std::size_t uns::FormPart::GetDataSize() const +{ + return pimpl->webcc_form->GetDataSize(); +} + +uns::Request::Request(std::unique_ptr impl) : pimpl(std::move(impl)) +{ +} + +uns::Request::~Request() = default; + +namespace uns +{ + std::string Request::GetMethodS() const + { + return pimpl->webcc_req->method(); + } + + std::string_view Request::GetMethod() const + { + return pimpl->webcc_req->method(); + } + + std::string Request::GetAddressS() const + { + return pimpl->webcc_req->address(); + } + + std::string_view Request::GetAddress() const + { + return pimpl->webcc_req->address(); + } + + size_t Request::GetContentLength() const + { + return pimpl->webcc_req->content_length(); + } + + std::string Request::GetDataS() const + { + return pimpl->webcc_req->data(); + } + + std::string_view Request::GetData() const + { + return pimpl->webcc_req->data(); + } + + std::string Request::GetSchemeS() const + { + return pimpl->webcc_req->url().scheme(); + } + + std::string_view Request::GetScheme() const + { + return pimpl->webcc_req->url().scheme(); + } + + std::string Request::GetHostS() const + { + return pimpl->webcc_req->host(); + } + + std::string_view Request::GetHost() const + { + return pimpl->webcc_req->host(); + } + + int Request::GetPortI() const + { + try + { + return std::stoi(pimpl->webcc_req->port(), nullptr, 10); + } + catch(const std::exception&) + { + return -1; + } + return -2; + } + + std::string Request::GetPortS() const + { + return pimpl->webcc_req->port(); + } + + std::string_view Request::GetPort() const + { + return pimpl->webcc_req->port(); + } + + std::string Request::GetPathS() const + { + return pimpl->webcc_req->url().path(); + } + + std::string_view Request::GetPath() const + { + return pimpl->webcc_req->url().path(); + } + + std::string Request::GetQueryStringS() const + { + return pimpl->webcc_req->url().query(); + } + + std::string_view Request::GetQueryString() const + { + return pimpl->webcc_req->url().query(); + } + + UrlQuery Request::GetQuery() const + { + auto query_impl = std::make_unique(pimpl->webcc_req->query()); + return UrlQuery(std::move(query_impl)); + } + + const std::vector& Request::GetArgs() const + { + return pimpl->webcc_req->args(); + } + + bool Request::IsForm() const + { + return pimpl->webcc_req->IsForm(); + } + + const std::vector& Request::GetFormParts() const + { + // 懒加载影子转换:仅在第一次访问且确属 Form 请求时构建镜像包装 + if (!pimpl->form_parts_cached) + { + if (pimpl->webcc_req->IsForm()) + { + const auto& raw_parts = pimpl->webcc_req->form_parts(); + pimpl->cached_form_parts.reserve(raw_parts.size()); + + for (const auto& raw_part : raw_parts) + { + auto part_impl = std::make_unique(raw_part); + // 由于 Request 是 FormPart 的友元,这里可以直接通过 new 访问其私有构造函数 + auto uns_part = std::shared_ptr(new FormPart(std::move(part_impl))); + pimpl->cached_form_parts.push_back(uns_part); + } + } + pimpl->form_parts_cached = true; + } + return pimpl->cached_form_parts; + } + + bool Request::HasHeader(std::string_view header) const + { + return pimpl->webcc_req->HasHeader(header); + } + + std::string Request::GetHeader(std::string_view header, bool* existed) const + { + return pimpl->webcc_req->GetHeader(header, existed); + } + + std::vector> Request::GetAllHeaders() const + { + return pimpl->webcc_req->GetAllHeaders().data(); + } +} + +uns::Response::Response(std::unique_ptr impl) : pimpl(std::move(impl)) +{ +} + +uns::Response::~Response() = default; diff --git a/UNSWebServerCore/HTTPObjects.h b/UNSWebServerCore/HTTPObjects.h new file mode 100644 index 0000000..0da37c8 --- /dev/null +++ b/UNSWebServerCore/HTTPObjects.h @@ -0,0 +1,336 @@ +#pragma once + +#include +#include +#include +#include +#include "Export.h" +#include + +class FileReceiver; + +namespace uns +{ + // HTTP status codes. + // Don't use "enum class" for converting to/from int easily. + // The full list is available here: + // https://en.wikipedia.org/wiki/List_of_HTTP_status_codes + enum Status + { + // 此临时响应表明客户端应继续请求,或者如果请求已完成,则忽略此响应。 + kContinue = 100, + // 此代码是在响应客户端的 Upgrade 请求标头时发送的,用于指示服务器即将切换到的协议。 + kSwitchingProtocols = 101, + // 此代码曾在 WebDAV 上下文中使用,表示服务器已收到请求,但在响应时无法提供状态。 + kProcessing = 102, + // 此状态码主要与 Link 标头一起使用,允许用户代理在服务器准备响应时开始预加载资源,或预连接到页面需要资源的源站。 + kEarlyHints = 103, + + // 请求成功。 + kOK = 200, + // 请求成功,并因此创建了一个新资源。 + kCreated = 201, + // 请求已被接收但尚未处理。 + kAccepted = 202, + // 此响应代码表示返回的元数据与原始服务器上可用的不完全相同,而是从本地或第三方副本收集的。这主要用于另一个资源的镜像或备份。 + kNonAuthoritativeInformation = 203, + // 对于此请求,没有内容可发送,但响应头可能有用。 + kNoContent = 204, + // 告知用户代理重置发送此请求的文档。 + kResetContent = 205, + // 当客户端请求了资源的一部分时,使用此响应代码进行响应。 + kPartialContent = 206, + // 在可能需要多个状态码的情况下,传递关于多个资源的信息。 + kMultiStatus = 207, + // 在 响应元素内部使用,以避免重复枚举同一集合的多个绑定的内部成员。 + kAlreadyReported = 208, + // 服务器已完成了对资源的 GET 请求,并且响应是对当前实例应用了一个或多个实例操作后的结果表示。 + kIMUsed = 226, + + // 在代理驱动(agent-driven)的内容协商中,请求有多个可能的响应,用户代理或用户应选择其中之一。 + kMultipleChoices = 300, + // 请求资源的 URL 已永久更改。新 URL 在响应中给出。 + kMovedPermanently = 301, + // 此响应代码意味着请求资源的 URI 已暂时更改。未来可能还会对 URI 进行进一步更改,因此客户端在未来的请求中应使用相同的 URI。 + kFound = 302, + // 服务器发送此响应以指示客户端使用 GET 请求在另一个 URI 获取请求的资源。 + kSeeOther = 303, + // 用于缓存目的。它告知客户端响应未被修改,因此客户端可以继续使用相同的缓存响应版本。 + kNotModified = 304, + // 在 HTTP 规范的前一版本中定义,表示请求的响应必须通过代理访问。由于涉及代理带内配置的安全问题,此状态码已被弃用。 + kUseProxy = 305, + // 此响应代码不再使用,但被保留。它曾在 HTTP/1.1 规范的先前版本中使用。 + k__Unused = 306, + // 服务器发送此响应以指示客户端使用与先前请求相同的方法在另一个 URI 获取请求的资源。其语义与 302 Found 响应代码相同,但用户代理不得更改使用的 HTTP 方法:如果在第一个请求中使用了 POST,则在重定向请求中也必须使用 POST。 + kTemporaryRedirect = 307, + // 表示资源现在永久位于另一个 URI,由 Location 响应头指定。其语义与 301 Moved Permanently HTTP 响应代码相同,但用户代理不得更改使用的 HTTP 方法:如果在第一个请求中使用了 POST,则在第二个请求中也必须使用 POST。 + kPermanentRedirect = 308, + + // 由于被认为是客户端错误的原因(例如,格式错误的请求语法、无效的请求消息结构或欺骗性的请求路由),服务器无法或不会处理该请求。 + kBadRequest = 400, + // 尽管 HTTP 标准指定为 "unauthorized",但从语义上讲,此响应的意思是 "unauthenticated"。即,客户端必须进行身份验证才能获得请求的响应。 + kUnauthorized = 401, + // 此代码最初用于数字支付系统,但此状态码很少使用,且不存在标准约定。 + kPaymentRequired = 402, + // 客户端没有访问内容的权利;也就是说,它是未授权的,因此服务器拒绝提供请求的资源。与 401 Unauthorized 不同,服务器知道客户端的身份。 + kForbidden = 403, + // 服务器找不到请求的资源。 + kNotFound = 404, + // 服务器知道请求方法,但目标资源不支持该方法。 + kMethodNotAllowed = 405, + // 当 Web 服务器执行服务器驱动的内容协商后,找不到任何符合用户代理给定条件的内容时,会发送此响应。 + kNotAcceptable = 406, + // 类似于 401 Unauthorized,但需要通过代理进行身份验证。 + kProxyAuthenticationRequired = 407, + // 某些服务器会在空闲连接上发送此响应,即使客户端之前没有任何请求。这意味着服务器希望关闭此未使用的连接。 + kRequestTimeout = 408, + // 当请求与服务器的当前状态冲突时,发送此响应。 + kConflict = 409, + // 当请求的内容已从服务器永久删除,且没有转发地址时,发送此响应。 + kGone = 410, + // 服务器拒绝了请求,因为未定义 Content-Length 标头字段,而服务器需要它。 + kLengthRequired = 411, + // 在条件请求中,客户端在其标头中指明了服务器不满足的前提条件。 + kPreconditionFailed = 412, + // 请求体大于服务器定义的限制。 + kContentTooLarge = 413, + // 客户端请求的 URI 长度超过了服务器愿意解释的长度。 + kURITooLong = 414, + // 服务器不支持请求数据的媒体格式,因此服务器拒绝该请求。 + kUnsupportedMediaType = 415, + // 无法满足请求中 Range 标头字段指定的范围。可能范围超出了目标资源数据的大小。 + kRangeNotSatisfiable = 416, + // 此响应代码表示服务器无法满足 Expect 请求标头字段指示的期望。 + kExpectationFailed = 417, + // 服务器拒绝尝试用茶壶煮咖啡。 + kIamATeapot = 418, + // 请求被发送到了一个无法产生响应的服务器。 + kMisdirectedRequest = 421, + // 请求格式正确,但由于语义错误而无法被遵循。 + kUnprocessableContent = 422, + // 正在访问的资源已被锁定。 + kLocked = 423, + // 由于先前的请求失败,导致当前请求失败。 + kFailedDependency = 424, + // 表示服务器不愿意冒险处理一个可能被重放的请求。 + kTooEarly = 425, + // 服务器拒绝使用当前协议执行请求,但可能在客户端升级到其他协议后愿意执行。服务器在 426 响应中发送 Upgrade 标头以指示所需的协议。 + kUpgradeRequired = 426, + // 原始服务器要求请求是有条件的。此响应旨在防止"丢失更新"问题,即客户端 GET 资源状态,修改后 PUT 回服务器,而同时第三方已修改了服务器上的状态,导致冲突。 + kPreconditionRequired = 428, + // 用户在给定的时间内发送了太多请求(速率限制)。 + kTooManyRequests = 429, + // 服务器因请求头字段太大而不愿意处理该请求。 + kRequestHeaderFieldsTooLarge = 431, + // 用户代理请求了一个无法合法提供的资源,例如被政府审查的网页。 + kUnavailableForLegalReasons = 451, + + // 服务器遇到了不知道如何处理的情况。此错误是通用性的,表示服务器找不到更合适的 5XX 状态码来响应。 + kInternalServerError = 500, + // 服务器不支持请求方法,无法处理。 + kNotImplemented = 501, + // 此错误响应意味着服务器作为网关或代理时,收到了一个无效的响应。 + kBadGateway = 502, + // 服务器尚未准备好处理请求。 + kServiceUnavailable = 503, + // 当服务器作为网关或代理,无法及时获得响应时,会给出此错误响应。 + kGatewayTimeout = 504, + // 服务器不支持请求中使用的 HTTP 版本。 + kHTTPVersionNotSupported = 505, + // 服务器存在内部配置错误:在内容协商过程中,被选中的变体被配置为自身参与内容协商,这导致在创建响应时出现循环引用。 + kVariantAlsoNegotiates = 506, + // 由于服务器无法存储成功完成请求所需的表示,因此无法对资源执行该方法。 + kInsufficientStorage = 507, + // 服务器在处理请求时检测到无限循环。 + kLoopDetected = 508, + // 客户端请求声明了一个应使用 HTTP 扩展(RFC 2774)来处理请求,但该扩展不受支持。 + kNotExtended = 510, + // 表示客户端需要进行身份验证才能获得网络访问权限。 + kNetworkAuthenticationRequired = 511, + + //Not Standard Code By UnknownObject + + // 请求载体的格式错误,如:无法解析的JSON等。 + k_uRequestFormatError = 489, + // 请求无效。可能是由于未正确携带数据等必要信息。 + k_uRequestInvalid = 490, + // 请求URL超范围。此响应表示请求的URL是错误的。 + k_uURLOutOfRange = 492, + // 无效的请求主机。指示请求时使用了错误的域名/IP。 + k_uInvalidRequestHost = 493, + // IP地址被封禁。 + k_uIPBlocked = 494, + // 非法上传请求。指示本次上传请求不符合服务器规定。 + k_uIllegalUpload = 495, + // 文件格式错误。指示上传的文件格式不符合服务器规定。 + k_uFileFormatError = 496, + // 无效文件。处理请求所需的文件已过期/无法访问。 + k_uInvalidFile = 497, + // 上传的文件过大。非文件上传时应使用 413 Content Too Large。 + k_uFileTooLarge = 498, + // 每秒请求数过多。仅在一些特殊API中使用,常规情况需使用 429 Too Many Requests。 + k_uRPSLimited = 499, + + // 子过程失败。服务器在处理请求的某个步骤中遇到无法恢复的错误。 + k_uSubProcessFalied = 533, + // 服务器检测到漏洞利用/可执行文件上传等网络攻击行为。 + k_uServerHateYou = 540, + // 检测到拒绝服务漏洞攻击。 + k_uDoSFound = 550, + // 检测到分布式拒绝服务漏洞攻击。 + k_uDDoSFound = 551, + // 未知的服务器错误。当服务器无法定位错误来源时返回。否则应使用 500 Internal Server Error。 + k_uUnknownServerError = 560 + }; + + // 路径穿透防御等级 + enum class PathTraversalDefenceLevel + { + DenyAll, //拒绝所有带有对应特征的路径 + AutoNormalize, //允许普通路径穿透,自动归一化,并要求进行安全路径检查 + AllowNormal, //允许普通路径穿透,原样返回,要求进行安全路径检查 + AllowAll [[deprecated("警告: AllowAll 已启用 - 路径穿透防御关闭 - 仅供测试环境使用.")]] //允许所有行为(仅供测试) + }; + + class UNSWSC_DLL_EXPORT UrlQuery + { + public: + UrlQuery(); + ~UrlQuery(); + + // 显式支持深拷贝与移动,内部完美同步 webcc 状态 + UrlQuery(const UrlQuery& other); + UrlQuery& operator=(const UrlQuery& other); + UrlQuery(UrlQuery&& other) noexcept; + UrlQuery& operator=(UrlQuery&& other) noexcept; + + public: + bool Empty() const; + std::size_t Size() const; + bool Has(const std::string& key) const; + const std::string& Get(const std::string& key) const; + std::pair Get(std::size_t index) const; + std::string ToString(bool encode = true) const; + + private: + friend class Request; + class Impl; + std::unique_ptr pimpl; + explicit UrlQuery(std::unique_ptr impl); + }; + + class UNSWSC_DLL_EXPORT FormPart + { + private: + class Impl; + + private: + friend class Request; + friend class ::FileReceiver; + + std::unique_ptr pimpl; + + private: + explicit FormPart(std::unique_ptr impl); + + public: + ~FormPart(); + + public: + std::string GetNameS() const; + std::string_view GetName() const; + std::string GetFileNameS() const; + std::string_view GetFileName() const; + std::string GetMediaTypeS() const; + std::string_view GetMediaType() const; + const std::string& GetData() const; + std::size_t GetSize() const; + std::size_t GetDataSize() const; + }; + + using FormPartPtr = std::shared_ptr; + + class UNSWSC_DLL_EXPORT Request + { + private: + class Impl; + friend class ServerCore; + friend class FileReceiverAdapter; + friend class ServerProcessorAdapter; + friend class SyncFileReceiverAdapter; + + std::unique_ptr pimpl; + + private: + explicit Request(std::unique_ptr impl); + + public: + ~Request(); + + Impl* GetImpl() const + { + return pimpl.get(); + } + + public: + // 基础请求信息 + std::string GetMethodS() const; + std::string_view GetMethod() const; + std::string GetAddressS() const; + std::string_view GetAddress() const; + size_t GetContentLength() const; + std::string GetDataS() const; + std::string_view GetData() const; + + // 扁平化后的 Url 核心数据项 + std::string GetSchemeS() const; + std::string_view GetScheme() const; + std::string GetHostS() const; + std::string_view GetHost() const; + int GetPortI() const; + std::string GetPortS() const; + std::string_view GetPort() const; + std::string GetPathS() const; + std::string_view GetPath() const; + std::string GetQueryStringS() const; + std::string_view GetQueryString() const; + + // 复杂复合对象获取 + UrlQuery GetQuery() const; + const std::vector& GetArgs() const; + + // 多部分表单(文件上传)支撑 + bool IsForm() const; + const std::vector& GetFormParts() const; + + // 请求头获取 + bool HasHeader(std::string_view header) const; + std::string GetHeader(std::string_view header, bool* existed = nullptr) const; + std::vector> GetAllHeaders() const; + }; + + class UNSWSC_DLL_EXPORT Response + { + private: + class Impl; + + private: + friend class ResponseBuilder; + + std::unique_ptr pimpl; + + private: + explicit Response(std::unique_ptr impl); + + public: + ~Response(); + + Impl* GetImpl() const + { + return pimpl.get(); + } + }; + + using RequestPtr = std::shared_ptr; + using ResponsePtr = std::shared_ptr; +} \ No newline at end of file diff --git a/UNSWebServerCore/HTTPObjectsBridge.h b/UNSWebServerCore/HTTPObjectsBridge.h new file mode 100644 index 0000000..b53204f --- /dev/null +++ b/UNSWebServerCore/HTTPObjectsBridge.h @@ -0,0 +1,62 @@ +#pragma once +#include +#include +#include "HTTPObjects.h" // 引入刚才的外观声明 + +class uns::UrlQuery::Impl +{ +public: + webcc::UrlQuery webcc_query; + + explicit Impl(const webcc::UrlQuery& query) : webcc_query(query) + { + } +}; + +class uns::FormPart::Impl +{ +public: + webcc::FormPartPtr webcc_form; + + explicit Impl(webcc::FormPartPtr res) : webcc_form(res) + { + } +}; + +// 在私有头文件里,明确定义外部壳类的 Impl 内部结构 +class uns::Request::Impl +{ +public: + webcc::RequestPtr webcc_req; + + // 影子缓存机制:用于解决 webcc 集合类型到外壳集合类型的无损生命周期映射 + mutable std::vector cached_form_parts; + mutable bool form_parts_cached = false; + + explicit Impl(webcc::RequestPtr req) : webcc_req(req) + { + } +}; + +class uns::Response::Impl +{ +public: + webcc::ResponsePtr webcc_res; + + explicit Impl(webcc::ResponsePtr res) : webcc_res(res) + { + } +}; + +namespace uns +{ + inline webcc::Status ConvertStatus(uns::Status s) + { + return static_cast(static_cast(s)); + } + + inline uns::Status ConvertStatus(webcc::Status s) + { + return static_cast(static_cast(s)); + } +} diff --git a/UNSWebServerCore/IPList.cpp b/UNSWebServerCore/IPList.cpp new file mode 100644 index 0000000..188e2eb --- /dev/null +++ b/UNSWebServerCore/IPList.cpp @@ -0,0 +1,106 @@ +#include "IPList.h" +#include + +bool IPList::CheckIPv4(std::string ip) +{ + std::regex v4("^(((\\d{1,2})|(1\\d{2})|(2[0-4]\\d)|(25[0-5]))\\.){3}((\\d{1,2})|(1\\d{2})|(2[0-4]\\d)|(25[0-5]))$"); + return std::regex_match(ip, v4); +} + +IPList::IPList() +{ + list.clear(); +} + +IPList::IPList(std::initializer_list list) +{ + for (const auto& ele : list) + this->list.insert(ele); +} + +IPList::IPList(const IPList& obj) +{ + for (const auto& ele : obj.list) + list.insert(ele); +} + +bool IPList::Push(std::string ip) +{ + if (!CheckIPv4(ip)) + return false; + list.insert(ip); + return true; +} + +bool IPList::Pop(std::string ip) +{ + if (!Exist(ip)) + return false; + list.erase(ip); + return false; +} + +bool IPList::Exist(std::string ip) +{ + return (list.find(ip) != list.end()); +} + +bool IPList::Exist(std::string ip) const +{ + return (list.find(ip) != list.end()); +} + +bool IPList::Empty() +{ + return list.empty(); +} + +size_t IPList::Size() +{ + return list.size(); +} + +auto IPList::begin() +{ + return list.begin(); +} + +auto IPList::end() +{ + return list.end(); +} + +auto IPList::rbegin() +{ + return list.rbegin(); +} + +auto IPList::rend() +{ + return list.rend(); +} + +auto IPList::begin() const +{ + return list.begin(); +} + +auto IPList::end() const +{ + return list.end(); +} + +auto IPList::rbegin() const +{ + return list.rbegin(); +} + +auto IPList::rend() const +{ + return list.rend(); +} + +bool IPList::operator<(const IPList& obj) const +{ + return list < obj.list; +} diff --git a/UNSWebServerCore/IPList.h b/UNSWebServerCore/IPList.h new file mode 100644 index 0000000..3a5fa21 --- /dev/null +++ b/UNSWebServerCore/IPList.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include "Export.h" +#include + +class UNSWSC_DLL_EXPORT IPList +{ +private: + std::set list; + +private: + bool CheckIPv4(std::string ip); + +public: + IPList(); + IPList(std::initializer_list list); + IPList(const IPList& obj); + +public: + bool Push(std::string ip); + bool Pop(std::string ip); + bool Exist(std::string ip); + bool Exist(std::string ip) const; + bool Empty(); + size_t Size(); + +public: + auto begin(); + auto end(); + auto rbegin(); + auto rend(); + auto begin() const; + auto end() const; + auto rbegin() const; + auto rend() const; + +public: + bool operator<(const IPList& obj) const; +}; + diff --git a/UNSWebServerCore/IPTable.cpp b/UNSWebServerCore/IPTable.cpp new file mode 100644 index 0000000..3e64bd8 --- /dev/null +++ b/UNSWebServerCore/IPTable.cpp @@ -0,0 +1,34 @@ +#include "IPTable.h" + +IPTable::IPTable() +{ + storage.clear(); +} + +IPTable::IPTable(const IPTable& obj) +{ + for (auto& ele : obj.storage) + storage.insert(ele); +} + +void IPTable::Appened(DateTime expr_time, IPList list) +{ + storage.insert(std::pair(expr_time, list)); +} + +void IPTable::Update() +{ + DateTime time = DateTime::Now(); + for (auto it = storage.begin(); it != storage.end(); it++) + if ((*it).first.GetTimeStamp() <= time.GetTimeStamp()) + storage.erase(it); + return; +} + +bool IPTable::IPExist(std::string ip) +{ + for (auto& ele : storage) + if (ele.second.Exist(ip)) + return true; + return false; +} diff --git a/UNSWebServerCore/IPTable.h b/UNSWebServerCore/IPTable.h new file mode 100644 index 0000000..e27dbed --- /dev/null +++ b/UNSWebServerCore/IPTable.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include "IPList.h" +#include "DateTime.h" + +class UNSWSC_DLL_EXPORT IPTable +{ +private: + std::set> storage; + +public: + IPTable(); + IPTable(const IPTable& obj); + +public: + void Appened(DateTime expr_time, IPList list); + void Update(); + bool IPExist(std::string ip); +}; + +using IPTablePtr = IPTable*; +//using IPTablePtr = std::shared_ptr; \ No newline at end of file diff --git a/UNSWebServerCore/LogArg.cpp b/UNSWebServerCore/LogArg.cpp new file mode 100644 index 0000000..89ad070 --- /dev/null +++ b/UNSWebServerCore/LogArg.cpp @@ -0,0 +1,15 @@ +#include "LogArg.h" +#ifndef _WIN32 +#include "utextcodec/UTextCodec.h" +#else +#include "../UTextCodec/UTextCodec.h" +#endif + +namespace uns +{ + // 宽字符转 UTF-8 实现 + std::string ConvertWStringToUtf8(std::wstring_view wstr) + { + return UTextCodec::WtoS(std::wstring(wstr)); + } +} diff --git a/UNSWebServerCore/LogArg.h b/UNSWebServerCore/LogArg.h new file mode 100644 index 0000000..8567482 --- /dev/null +++ b/UNSWebServerCore/LogArg.h @@ -0,0 +1,254 @@ +#pragma once +#include +#include +#include +#include +#include +#include "Export.h" +#include +#include +#include + +namespace uns +{ + // 前置声明捕获器 + struct RangeCapturer; + struct PairCapturer; + struct FuncCapturer; + struct TimeCapturer; + + // 终极 Variant 池:容纳所有可能解包出的原子形态 + using LogVariant = std::variant< + bool, char, int, unsigned int, long long, unsigned long long, double, std::string_view, const void*, + std::string, // 专门用于承载宽字符转换后的 UTF-8 临时字符串 + RangeCapturer, // 专门用于承载 std::vector / std::list 等容器 + PairCapturer, // 专门用于承载 std::pair (支持 std::map) + FuncCapturer, //承载 std::function + TimeCapturer //承载时间 + >; + + // --- 编译期类型推导工具链 --- + template + struct is_container : std::false_type + { + }; + + // 识别标准容器(排除字符串本身) + template + struct is_container().begin()), decltype(std::declval().end())>> : std::integral_constant && !std::is_same_v> + { + }; + + template struct is_pair : std::false_type + { + }; + + template struct is_pair> : std::true_type + { + }; + + // --- 延迟桥接结构体 --- + struct LogArg; + + struct RangeCapturer + { + const void* ptr; + void (*to_log_args)(const void*, std::vector&); + }; + + struct PairCapturer + { + const void* ptr; + void (*to_log_args)(const void*, std::vector&); + }; + + struct FuncCapturer + { + const void* code_ptr; // 真正改变:这里存真正的函数代码地址 + bool is_closure; // 标记:这究竟是个纯函数,还是个 Lambda 闭包 + }; + + // 升级版 Trait:不仅识别,还提取原生函数指针类型(R(*)(Args...)) + template struct function_traits + { + static constexpr bool is_func = false; + using pointer_type = void*; + }; + + template + struct function_traits> + { + static constexpr bool is_func = true; + using pointer_type = R(*)(Args...); // 提取出原生函数指针类型 + }; + + enum class TimeMode + { + Point, + TMPoint, + Duration + }; + + struct TimeCapturer + { + TimeMode mode{ TimeMode::Point }; + + std::tm _tm{}; + std::chrono::system_clock::time_point tp{}; + std::chrono::nanoseconds duration{ 0 }; + + std::string format_spec; // "%Y-%m-%d %H:%M:%S" or empty + + // 可选:单位策略控制 + bool smart_unit = true; + }; + + template + struct is_time_point : std::false_type + { + }; + + template + struct is_time_point> : std::true_type + { + }; + + template<> + struct is_time_point : std::true_type + { + }; + + template + inline constexpr bool is_time_point_v = is_time_point::value; + + template + struct is_duration : std::false_type + { + }; + + template + struct is_duration> : std::true_type + { + }; + + template + inline constexpr bool is_duration_v = is_duration::value; + + inline TimeCapturer MakeTimeCapturer(std::chrono::system_clock::time_point tp) + { + TimeCapturer c; + c.mode = TimeMode::Point; + c.tp = tp; + return c; + } + + template + inline TimeCapturer MakeTimeCapturer(std::chrono::duration d) + { + TimeCapturer c; + c.mode = TimeMode::Duration; + c.duration = std::chrono::duration_cast(d); + c.smart_unit = true; + return c; + } + + inline TimeCapturer MakeTimeCapturer(const std::tm& tm) + { + TimeCapturer c; + c.mode = TimeMode::TMPoint; + c._tm = tm; + return c; + } + + // --- 宽字符转换声明(实现卸载到 .cpp) --- + std::string UNSWSC_DLL_EXPORT ConvertWStringToUtf8(std::wstring_view wstr); + + // --- 核心包装类 --- + struct LogArg + { + LogVariant value; + + template + LogArg(T&& val) + { + using D = std::decay_t; + + // 1. 窄字符串系列(0拷贝) + if constexpr (std::is_same_v || std::is_same_v) + value = std::string_view(val); + else if constexpr (std::is_same_v || std::is_same_v) + value = val ? std::string_view(val) : std::string_view(""); + // 2. 宽字符串系列(特殊处理:触发内部 UTF-8 转换) + else if constexpr (std::is_same_v || std::is_same_v) + value = ConvertWStringToUtf8(val); + else if constexpr (std::is_same_v || std::is_same_v) + value = val ? ConvertWStringToUtf8(val) : std::string(""); + // 3. 基础原子类型(防范 wchar_t 被误判为整数) + else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + value = ConvertWStringToUtf8(std::wstring_view((const wchar_t*)&val, 1)); + else if constexpr (std::is_same_v) + value = static_cast(val); + else if constexpr (std::is_same_v || std::is_same_v) + value = static_cast(val); + // 3.5 时间点类型系列(防止时间对象退化为未知类型) + else if constexpr (is_time_point_v) + value = MakeTimeCapturer(val); + else if constexpr (is_duration_v) + value = MakeTimeCapturer(val); + // 4. 全整型变种矩阵 (short, int, long, long long, unsigned...) + else if constexpr (std::is_integral_v && std::is_signed_v) + { + if constexpr (sizeof(D) <= sizeof(int)) + value = static_cast(val); + else + value = static_cast(val); + } + else if constexpr (std::is_integral_v && std::is_unsigned_v) + { + if constexpr (sizeof(D) <= sizeof(unsigned int)) + value = static_cast(val); + else + value = static_cast(val); + } + // 5. 浮点矩阵 + else if constexpr (std::is_floating_point_v) + value = static_cast(val); + // 6. 指针 + else if constexpr (std::is_pointer_v) + value = static_cast(val); + // 7. 标准库容器(关键点:利用 Lambda 闭包在不引入 fmt 的情况下擦除容器类型!) + else if constexpr (is_container::value) + { + value = RangeCapturer{ &val, [] (const void* p, std::vector& out) + { + for (const auto& item : *static_cast(p)) + out.emplace_back(item); // 递归包装子元素 + } + }; + } + // 8. 键值对(支持 Map 展开) + else if constexpr (is_pair::value) + { + value = PairCapturer{ &val, [] (const void* p, std::vector& out) + { + const auto& pair = *static_cast(p); + out.emplace_back(pair.first); + out.emplace_back(pair.second); + } + }; + } + else if constexpr (function_traits::is_func) + { + using TargetPtr = typename function_traits::pointer_type; + // 关键点:尝试用 target() 拿底层指针 + // std::function::target() 返回的是 T*,所以如果 T 是函数指针,返回的就是“函数指针的指针” + if (auto* const* func_ptr = val.template target()) // 情况 A:内部装的是普通纯函数或静态成员函数 + value = FuncCapturer{ reinterpret_cast(*func_ptr), false }; + else // 情况 B:内部装的是 Lambda 表达式或带状态的仿函数, 此时它在堆/栈上有一个闭包实体,我们退而求其次,打印这个闭包对象的地址 + value = FuncCapturer{ static_cast(&val), true }; + } + else + value = std::string_view(""); + } + }; +} \ No newline at end of file diff --git a/UNSWebServerCore/PathTraversal.cpp b/UNSWebServerCore/PathTraversal.cpp new file mode 100644 index 0000000..facd02d --- /dev/null +++ b/UNSWebServerCore/PathTraversal.cpp @@ -0,0 +1,175 @@ +#include "PathTraversal.h" +#include +#include + +bool PathTraversal::InlineHasTraversalPattern(std::string_view path) +{ + if (path == "..") + return true; + if (path.rfind("../", 0) == 0) + return true; + if (path.find("/../") != std::string::npos) + return true; + if (path.size() >= 3 && path.compare(path.size() - 3, 3, "/..") == 0) + return true; + return false; +} + +std::string PathTraversal::UrlDecode(std::string_view src) +{ + std::string dst; + dst.reserve(src.length()); // 预分配内存,优化性能 + + for (size_t i = 0; i < src.length(); ++i) + { + // 确保 % 后面至少还有两个字符 + if ((src[i] == '%') && ((i + 2) < src.length())) + { + // 转换为 unsigned char 规避 std::isxdigit 在处理非 ASCII(如UTF-8) 时的未定义行为 + unsigned char hi = static_cast(src[i + 1]); + unsigned char lo = static_cast(src[i + 2]); + + if (std::isxdigit(hi) && std::isxdigit(lo)) + { + auto hexToChar = [] (unsigned char c) -> int + { + if ((c >= '0') && (c <= '9')) + return c - '0'; + if ((c >= 'a') && (c <= 'f')) + return c - 'a' + 10; + if ((c >= 'A') && (c <= 'F')) + return c - 'A' + 10; + return 0; + }; + dst += static_cast((hexToChar(hi) << 4) | hexToChar(lo)); + i += 2; + continue; + } + } + dst += src[i]; + } + return dst; +} + +bool PathTraversal::HasPathTraversalPattern(std::string_view raw_url_path) +{ + if (raw_url_path.empty()) + return false; + // 1. 进行 URL 解码,让隐藏的 %2f, %2e 现出原形 + std::string decoded = UrlDecode(raw_url_path); + // 2. 统一将 Windows 风格的反斜杠 `\` 替换为正斜杠 `/`,防止利用反斜杠绕过 + std::replace(decoded.begin(), decoded.end(), '\\', '/'); + // 3. 特征级严格匹配: + // 路径穿透的本质是形成一个独立的 ".." 路径层级。 + // 它只可能以四种形态存在:".."、"../开头的路径"、"/../中间路径"、以及"/.."结尾的路径。 + if (decoded == "..") + return true; + if (decoded.rfind("../", 0) == 0) // 检查是否以 "../" 开头 + return true; + if (decoded.find("/../") != std::string::npos) // 检查是否包含 "/../" + return true; + if (decoded.size() >= 3 && decoded.compare(decoded.size() - 3, 3, "/..") == 0) // 检查是否以 "/.." 结尾 + return true; + return false; +} + +bool PathTraversal::IsSafePath(const std::filesystem::path& base_path, const std::filesystem::path& user_path) +{ + namespace fs = std::filesystem; + try + { + // 1. 规范化沙盒基准路径 + fs::path canonical_base = fs::weakly_canonical(base_path); + // 2. 拼接并规范化目标路径 + // 注意:若 user_path 为绝对路径(如 /etc/passwd),operator/ 会直接覆盖前面的路径, + // weakly_canonical 依然能正确将其解析为最终的物理绝对路径。 + fs::path canonical_target = fs::weakly_canonical(canonical_base / user_path); + // 3. 计算逻辑相对关系 + auto rel = canonical_target.lexically_relative(canonical_base); + // 4. 边界一票否决制: + // - 如果 rel 为空,说明两者不在同一根目录下(例如 Windows 跨盘符 C:\ 到 D:\) + // - 如果相对路径的第一个组件是 "..", 说明逻辑路径已经逃逸出根目录 + if (rel.empty() || *rel.begin() == "..") + return false; + return true; + } + catch (...) + { + // 捕获任何潜在的系统路径解析异常(如超长路径、非法字符等) + return false; + } +} + +PathTraversal::UrlSafetyStatus PathTraversal::AnalyzeUrlTraversal(std::string_view raw_url_path) +{ + // 1. 检查原始明文中是否存在穿透特征(统一斜杠处理) + std::string raw_normalized_slash(raw_url_path); + std::replace(raw_normalized_slash.begin(), raw_normalized_slash.end(), '\\', '/'); + bool raw_has_traversal = InlineHasTraversalPattern(raw_normalized_slash); + // 2. 进行 URL 解码,让隐藏的 %2f, %2e, %5c 现出原形 + std::string decoded_path = UrlDecode(raw_url_path); + std::replace(decoded_path.begin(), decoded_path.end(), '\\', '/'); + bool decoded_has_traversal = InlineHasTraversalPattern(decoded_path); + // 3. 对比前后差异 + if (decoded_has_traversal) + { + if (!raw_has_traversal) // 【核心逻辑】明文看起来很安全,解码后突然蹦出穿透特征 -> 判定为转义绕过攻击 + return UrlSafetyStatus::EvasiveTraversal; + // 明文和密文都有,属于客户端手荡或老旧 SDK 拼接未规范化 + return UrlSafetyStatus::LiteralTraversal; + } + return UrlSafetyStatus::Safe; +} + +std::string PathTraversal::NormalizeUrlPath(std::string_view decoded_url_path) +{ + if (decoded_url_path.empty()) + return "/"; + std::vector segments; + size_t start = 0; + // 逻辑切分路径组件 + while (start < decoded_url_path.length()) + { + size_t end = decoded_url_path.find_first_of("/\\", start); + std::string_view segment = decoded_url_path.substr(start, end - start); + + if (!segment.empty() && segment != ".") + { + if (segment == "..") + { + // 遇到 .. 则弹栈,实现路径向上坍缩 + if (!segments.empty()) + segments.pop_back(); + } + else + segments.push_back(segment); + } + if (end == std::string_view::npos) + break; + start = end + 1; + } + // 重新拼接规范化后的标准 URL 路径 + std::string result; + for (const auto& seg : segments) + { + result += "/"; + result.append(seg); + } + return result.empty() ? "/" : result; +} + +std::string PathTraversal::ToString(UrlSafetyStatus s) +{ + switch(s) + { + case UrlSafetyStatus::Safe: + return "Safe"; + case UrlSafetyStatus::LiteralTraversal: + return "LiteralTraversal"; + case UrlSafetyStatus::EvasiveTraversal: + return "EvasiveTraversal"; + default: + return "Unknown??"; + } + return "Unknown??"; +} diff --git a/UNSWebServerCore/PathTraversal.h b/UNSWebServerCore/PathTraversal.h new file mode 100644 index 0000000..4203b31 --- /dev/null +++ b/UNSWebServerCore/PathTraversal.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include + +class PathTraversal +{ +public: + enum class UrlSafetyStatus + { + Safe, // 1. 完全安全:无任何穿透特征 + LiteralTraversal, // 2. 明文穿透:URL 包含裸 ".."(老旧客户端未展开,内核可选择为其“规范化”后放行) + EvasiveTraversal // 3. 转义伪装:明文无异常,解码后才出现 ".."(100% 恶意攻击,内核应直接阻断并拉黑) + }; + +private: + /** + * @brief 内部辅助:检测已统一斜杠的字符串中是否包含标准的 ".." 路径层级 + */ + static bool InlineHasTraversalPattern(std::string_view path); + +public: + /** + * @brief 实用可靠的 URL 解码函数 (符合 RFC 3986 规范) + * @note 针对路径解析优化:未将 '+' 转换为系统空格('+'仅在 query 参数中代表空格,在 path 中代表字面量) + */ + static std::string UrlDecode(std::string_view src); + /** + * @brief 纯字符串层面的路径穿透行为检测 (内核特征级拦截) + * @param raw_url_path 客户端传入的原始未解码 URL 路径 (注意:必须是不包含 Query 参数的纯 Path 部分) + * @return true 存在路径穿透特征(危险);false 未检测到穿透特征(安全) + */ + static bool HasPathTraversalPattern(std::string_view raw_url_path); + /** + * @brief 基于物理与逻辑边界的路径穿透(目录穿越)安全校验 + * @param base_path 允许访问的沙盒根目录(绝对或相对路径均可) + * @param user_path 客户端传入的、解码后的目标子路径 + * @return true 安全(在沙盒内);false 不安全(企图穿越或路径非法) + */ + static bool IsSafePath(const std::filesystem::path& base_path, const std::filesystem::path& user_path); + /** + * @brief 函数 1:内核级明文与密文对比检测 + * @param raw_url_path 核心网关拿到的原始未解码、且已剥离 Query 参数的纯 Path 部分 + */ + static UrlSafetyStatus AnalyzeUrlTraversal(std::string_view raw_url_path); + /** + * @brief 函数 2:纯文本 URL 路径规范化 (实现 RFC 3986 逻辑) + * @details 剥离路径中所有的 "." 和 "..",将其坍缩为安全的绝对路由路径(例如:把 `/a/b/../c` 规范化为 `/a/c`) + * @param decoded_url_path 已经过安全校验并确认非恶意攻击的【已解码】路径 + */ + static std::string NormalizeUrlPath(std::string_view decoded_url_path); + + static std::string ToString(UrlSafetyStatus s); +}; + diff --git a/UNSWebServerCore/ProcessorAdapter.h b/UNSWebServerCore/ProcessorAdapter.h new file mode 100644 index 0000000..02d6bcf --- /dev/null +++ b/UNSWebServerCore/ProcessorAdapter.h @@ -0,0 +1,113 @@ +#pragma once + +#include "IPTable.h" +#include +#include "FileReceiver.h" +#include "ServerProcessor.h" +#include "HTTPObjectsBridge.h" // 确保能看到 Request::Impl 结构体 + +namespace uns +{ + class IBlockedIpUpdatable + { + public: + virtual ~IBlockedIpUpdatable() = default; + + // 统一的库内更新接口(这里的 YourIPContainerType 请替换为你 BlockedIPs 的实际类型) + virtual void ApplyIpUpdate(IPTablePtr blocked_ips) = 0; + }; + + // 隐藏在动态库内部的适配器,对外部不可见 + + class ServerProcessorAdapter : public webcc::View, public IBlockedIpUpdatable + { + private: + std::shared_ptr user_processor; + + public: + explicit ServerProcessorAdapter(std::shared_ptr processor) : user_processor(processor) + { + } + + // 完美的把 webcc 的驱动流,翻译给用户的纯净业务类 + webcc::ResponsePtr Handle(webcc::RequestPtr request) final + { + auto uns_req = uns::RequestPtr(new uns::Request(std::make_unique(request))); + // 调用用户的业务类 + uns::ResponsePtr uns_res = user_processor->Handle(uns_req); + return uns_res->GetImpl()->webcc_res; + } + + bool Stream(const std::string& method) final + { + return user_processor->Stream(method); + } + + void ApplyIpUpdate(IPTablePtr blocked_ips) override + { + user_processor->UpdateBlockedIPList(blocked_ips); + } + }; + + class FileReceiverAdapter : public webcc::View, public IBlockedIpUpdatable + { + private: + std::shared_ptr<::FileReceiver> user_reciver; // 持有用户的派生类对象 + + public: + explicit FileReceiverAdapter(std::shared_ptr reciver) : user_reciver(reciver) + { + } + + // 完美的把 webcc 的驱动流,翻译给用户的纯净业务类 + webcc::ResponsePtr Handle(webcc::RequestPtr request) final + { + auto uns_req = uns::RequestPtr(new uns::Request(std::make_unique(request))); + // 调用用户的业务类 + uns::ResponsePtr uns_res = user_reciver->Execute(uns_req); + return uns_res->GetImpl()->webcc_res; + } + + bool Stream(const std::string& method) final + { + // 所有数据都不能由webcc进行串流,否则将无法从request中获取文件 + return false; + } + + void ApplyIpUpdate(IPTablePtr blocked_ips) override + { + user_reciver->UpdateBlockedIPs(blocked_ips); + } + }; + + class SyncFileReceiverAdapter : public webcc::View, public IBlockedIpUpdatable + { + private: + std::shared_ptr user_reciver; // 持有用户的派生类对象 + + public: + explicit SyncFileReceiverAdapter(std::shared_ptr reciver) : user_reciver(reciver) + { + } + + // 完美的把 webcc 的驱动流,翻译给用户的纯净业务类 + webcc::ResponsePtr Handle(webcc::RequestPtr request) final + { + auto uns_req = uns::RequestPtr(new uns::Request(std::make_unique(request))); + // 调用用户的业务类 + uns::ResponsePtr uns_res = user_reciver->Execute(uns_req); + return uns_res->GetImpl()->webcc_res; + } + + bool Stream(const std::string& method) final + { + // 所有数据都不能由webcc进行串流,否则将无法从request中获取文件 + return false; + } + + void ApplyIpUpdate(IPTablePtr blocked_ips) override + { + user_reciver->UpdateBlockedIPs(blocked_ips); + } + }; +} \ No newline at end of file diff --git a/UNSWebServerCore/SafeRNG.cpp b/UNSWebServerCore/SafeRNG.cpp new file mode 100644 index 0000000..94053a3 --- /dev/null +++ b/UNSWebServerCore/SafeRNG.cpp @@ -0,0 +1,128 @@ +#include "SafeRNG.h" + +#include +#include +#include +#include +#include + +bool RandomNumberGenerator::FillWithOpenSSLRandom(unsigned char* dst, size_t len) noexcept +{ + try + { + while (len > 0) + { + int chunk = (len > static_cast(INT_MAX)) ? INT_MAX : static_cast(len); + // RAND_bytes 返回 1 成功 + int rc = RAND_bytes(dst, chunk); + if (rc != 1) + return false; + dst += chunk; + len -= static_cast(chunk); + } + return true; + } + catch (...) + { + return false; + } +} + +bool RandomNumberGenerator::FillWithSTDMT19937_64(unsigned char* dst, size_t len) noexcept +{ + try + { + // 先尝试用 random_device 为 mt19937_64 提供种子 + std::mt19937_64 gen; + try + { + std::random_device rd; + // 将几个 rd() 的值混合到一个 seed 中 + uint64_t seed = 0; + for (int i = 0; i < 4; ++i) + seed ^= (static_cast(rd()) + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2)); + gen.seed(seed); + } + catch (...) + { + // random_device 可能抛或不可靠;退回到时间戳种子(不可预测性较低) + uint64_t seed = static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); + gen.seed(seed); + } + + // 逐 8 字节生成并拷贝;最后处理剩余字节 + while (len >= 8) + { + uint64_t v = gen(); + std::memcpy(dst, &v, 8); + dst += 8; + len -= 8; + } + if (len > 0) + { + uint64_t v = gen(); + std::memcpy(dst, &v, len); + } + return true; + } + catch (...) + { + return false; + } +} + +std::string RandomNumberGenerator::Bytes2HexString(const unsigned char* data, size_t len) noexcept +{ + try + { + const char* hex_chars = "0123456789ABCDEF"; + std::string out; + out.resize(len * 2); + for (std::size_t i = 0; i < len; ++i) + { + unsigned char v = data[i]; + out[2 * i] = hex_chars[(v >> 4) & 0xF]; + out[2 * i + 1] = hex_chars[v & 0xF]; + } + return out; + } + catch (...) + { + return std::string(); + } +} + +std::string RandomNumberGenerator::SecureRandomHex(size_t bytes) +{ + if (bytes == 0) + return std::string(); + + // 分配原始缓冲区(可能抛 bad_alloc -> catch below) + unsigned char* buf = nullptr; + try + { + buf = static_cast(::operator new(bytes)); + } + catch (...) + { + return std::string(); // 无法分配内存,返回空字符串表示失败 + } + + bool ok = FillWithOpenSSLRandom(buf, bytes); + if (!ok) + { + //printf_s("OpenSSL Failure!\n"); + // OpenSSL 失败:使用伪随机兜底(保证会产出数据) + bool ok2 = FillWithSTDMT19937_64(buf, bytes); + if (!ok2) + { + // 极端失败:释放并返回空字符串 + ::operator delete(buf); + return std::string(); + } + } + // 转 hex 并返回(Bytes2HexString 会在异常时返回空字符串) + std::string hex = Bytes2HexString(buf, bytes); + ::operator delete(buf); + return hex; +} \ No newline at end of file diff --git a/UNSWebServerCore/SafeRNG.h b/UNSWebServerCore/SafeRNG.h new file mode 100644 index 0000000..a85d1ee --- /dev/null +++ b/UNSWebServerCore/SafeRNG.h @@ -0,0 +1,15 @@ +#pragma once +#include + +class RandomNumberGenerator +{ +private: + //主方案:OpenSSL,安全随机数,有极小概率失败 + static bool FillWithOpenSSLRandom(unsigned char* dst, size_t len) noexcept; + //备用方案:std::mt19937_64/时间戳伪随机数,不怎么安全但是一定有数 + static bool FillWithSTDMT19937_64(unsigned char* dst, size_t len) noexcept; + static std::string Bytes2HexString(const unsigned char* data, size_t len) noexcept; + +public: + static std::string SecureRandomHex(size_t bytes); +}; \ No newline at end of file diff --git a/UNSWebServerCore/ServerCore.cpp b/UNSWebServerCore/ServerCore.cpp new file mode 100644 index 0000000..38fc053 --- /dev/null +++ b/UNSWebServerCore/ServerCore.cpp @@ -0,0 +1,188 @@ +#include "ServerCore.h" +#include "Global.h" +#include "IPTable.h" +#include "ServerLogger.h" +#include "CORSProcessor.h" +#include +#include +#include +#include "ProcessorAdapter.h" + +class ServerCore::Impl +{ +public: + int port = 0; + webcc::Server* ccServer = nullptr; + std::thread thServer; + IPTable BlockedIPs; + +public: + explicit Impl(int port) + { + this->port = port; + ccServer = new webcc::Server(boost::asio::ip::tcp::v4(), port); + ccServer->SetDefaultServerName(G_SERVER_NAME); + webcc::utility::SetCustomUA(G_SERVER_NAME); + SCLOGF_INFO("ServerCore constructed on port {}, version: <{}>", port, G_SERVER_NAME); + } + + ~Impl() + { + if(ccServer != nullptr) + { + if(ccServer->IsRunning()) + ccServer->Stop(); + delete ccServer; + } + } + +public: + static void ServerThreadFunction(webcc::Server* server, int worker_thread, int loop_thread) + { + if (server == nullptr) + return; + server->set_buffer_size(10240); + SCLOGF_INFO("ServerCore thread ready: {} Worker(s), {} Loop(s)", worker_thread, loop_thread); + server->Run(worker_thread, loop_thread); + return; + } + + static webcc::Strings ConvertMethods(std::uint32_t dwmethod) + { + webcc::Strings res; + if (dwmethod & uns::H_GET) + res.push_back("GET"); + if (dwmethod & uns::H_PUT) + res.push_back("PUT"); + if (dwmethod & uns::H_HEAD) + res.push_back("HEAD"); + if (dwmethod & uns::H_POST) + res.push_back("POST"); + if (dwmethod & uns::H_TRACE) + res.push_back("TRACE"); + if (dwmethod & uns::H_PATCH) + res.push_back("PATCH"); + if (dwmethod & uns::H_DELETE) + res.push_back("DELETE"); + if (dwmethod & uns::H_OPTIONS) + res.push_back("OPTIONS"); + if (dwmethod & uns::H_CONNECT) + res.push_back("CONNECT"); + return res; + } +}; + +ServerCore::ServerCore(int port) : pimpl(std::make_unique(port)) +{ +} + +ServerCore::~ServerCore() = default; + +void ServerCore::Run(int worker_thread, int loop_thread) +{ + if (pimpl->ccServer == nullptr) + return; + pimpl->ccServer->set_buffer_size(10240); + SCLOGF_INFO("ServerCore ready: {} Worker(s), {} Loop(s)", worker_thread, loop_thread); + pimpl->ccServer->Run(worker_thread, loop_thread); + return; +} + +void ServerCore::ThreadRun(int worker_thread, int loop_thread) +{ + using namespace std::chrono; + if (pimpl->ccServer == nullptr) + return; + pimpl->thServer = std::thread(Impl::ServerThreadFunction, pimpl->ccServer, worker_thread, loop_thread); + pimpl->thServer.detach(); + std::this_thread::sleep_for(100ms); + if (pimpl->ccServer->IsRunning()) + SCLOG_INFO("ServerCore Running"); + return; +} + +void ServerCore::Stop() +{ + if (pimpl->ccServer == nullptr) + return; + pimpl->ccServer->Stop(); + SCLOG_INFO("ServerCore Stopped"); + return; +} + +bool ServerCore::Running() +{ + return pimpl->ccServer->IsRunning(); +} + +void ServerCore::UpdateProcessor() +{ + for (size_t i = 0; i < pimpl->ccServer->GetViewCount(); i++) + { + auto updatable_ptr = std::dynamic_pointer_cast(pimpl->ccServer->AccessView(i)); + if (updatable_ptr != nullptr) + updatable_ptr->ApplyIpUpdate(&pimpl->BlockedIPs); + } + SCLOG_TRACE("Blocked IP list updated"); + return; +} + +bool ServerCore::EnableCORSSupport() +{ + return AppenedProcessor(CORSProcessor::UrlRegex(), CORSProcessor::SharedPtr(), uns::H_OPTIONS); +} + +bool ServerCore::AppenedProcessor(std::string url, ServerProcessorPtr ptr, std::uint32_t methods, bool enable_ip_check) +{ + if (enable_ip_check) + { + ptr->EnableIPCheck(); + ptr->UpdateBlockedIPList(&pimpl->BlockedIPs); + } + auto adapter = std::make_shared(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()); + else + SCLOG_ERROR("ServerProcessor add faliure. URL: [%s], Method code: <%s>", url.c_str(), uns::toBinary(methods, 10).c_str()); + return bret; +} + +bool ServerCore::AppenedFileReceiver(std::string url, FileReceiverPtr ptr, std::uint32_t methods, FileProcessorCallback fpcb, bool html_response) +{ + ptr->SetFileCallback(fpcb); + ptr->UpdateBlockedIPs(&pimpl->BlockedIPs); + ptr->SetResponseMode(html_response); + auto adapter = std::make_shared(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()); + else + SCLOG_ERROR("FileReceiver add faliure. URL: [%s], Method code: <%s>", url.c_str(), uns::toBinary(methods, 10).c_str()); + return bret; +} + +bool ServerCore::AppenedFileReceiver(std::string url, SyncFileReceiverPtr ptr, std::uint32_t methods, bool html_response) +{ + ptr->UpdateBlockedIPs(&pimpl->BlockedIPs); + ptr->SetResponseMode(html_response); + auto adapter = std::make_shared(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()); + else + SCLOG_ERROR("SyncFileReceiver add faliure. URL: [%s], Method code: <%s>", url.c_str(), uns::toBinary(methods, 10).c_str()); + return bret; +} + +void ServerCore::EnableWebCCLog(const std::string& path, int level) +{ + if(path.empty()) + { + WEBCC_LOG_INIT_2("", webcc::LOG_CONSOLE, level); + } + else + { + WEBCC_LOG_INIT_2(path.c_str(), webcc::LOG_CONSOLE | webcc::LOG_CONSOLE_FILE_APPEND, level); + } +} diff --git a/UNSWebServerCore/ServerCore.h b/UNSWebServerCore/ServerCore.h new file mode 100644 index 0000000..1359bfe --- /dev/null +++ b/UNSWebServerCore/ServerCore.h @@ -0,0 +1,48 @@ +/* +* Unknown Network Service Web Server Core +* Version 1.2.2 +* +* TCP Core - boost::aiso +* HTTP Core - webcc +* Application Level Repack & Windows Service Interface - UnknownObject +*/ + +#pragma once +#include +#include +#include "Global.h" +#include "Export.h" +#include "FileReceiver.h" +#include "ServerProcessor.h" +#include "SyncFileReceiver.h" + +class UNSWSC_DLL_EXPORT ServerCore +{ +private: + class Impl; + std::unique_ptr pimpl; + +public: + ServerCore(int port); + ~ServerCore(); + ServerCore() = delete; + ServerCore(const ServerCore& obj) = delete; + ServerCore& operator=(const ServerCore& obj) = delete; + +public: + void Run(int worker_thread = 1, int loop_thread = 1); + void ThreadRun(int worker_thread = 1, int loop_thread = 1); + void Stop(); + bool Running(); + void UpdateProcessor(); + bool EnableCORSSupport(); + bool AppenedProcessor(std::string url, ServerProcessorPtr ptr, std::uint32_t methods, bool enable_ip_check = false); + bool AppenedFileReceiver(std::string url, FileReceiverPtr ptr, std::uint32_t methods, FileProcessorCallback fpcb, bool html_response = false); + bool AppenedFileReceiver(std::string url, SyncFileReceiverPtr ptr, std::uint32_t methods, bool html_response = false); + +public: + //启用WebCC日志 + //path: 日志存放文件夹,留空为仅控制台日志 + //level: 0-4, 0=VERB, 1=INFO, 2=USER(default), 3=WARN, 4=ERROR + static void EnableWebCCLog(const std::string& path = "", int level = 2); +}; \ No newline at end of file diff --git a/UNSWebServerCore/ServerLogger.cpp b/UNSWebServerCore/ServerLogger.cpp new file mode 100644 index 0000000..5cf014c --- /dev/null +++ b/UNSWebServerCore/ServerLogger.cpp @@ -0,0 +1,704 @@ +#include "ServerLogger.h" +#include +#include +#include +#include +#include +#include + +// 格式化用的辅助函数 + +struct DurationView +{ + long double value; + const char* unit; +}; + +inline std::tm ToTm(std::chrono::system_clock::time_point tp) +{ + return fmt::localtime(std::chrono::system_clock::to_time_t(tp)); +} + +inline std::string FormatTime(const std::tm& tm, const std::string& spec) +{ + char buf[128] = {}; + + const char* fmt = spec.empty() ? "%Y-%m-%d %H:%M:%S" : spec.c_str(); + + std::strftime(buf, sizeof(buf), fmt, &tm); + return buf; +} + +inline DurationView NormalizeDuration(std::chrono::nanoseconds ns) +{ + long double v = (long double)ns.count(); + + constexpr long double ns_1 = 1.0L; + constexpr long double us = 1000.0L; + constexpr long double ms = 1000000.0L; + constexpr long double s = 1000000000.0L; + constexpr long double m = 60.0L * s; + constexpr long double h = 60.0L * m; + constexpr long double d = 24.0L * h; + + if (v >= d) + return { v / d, "d" }; + if (v >= h) + return { v / h, "h" }; + if (v >= m) + return { v / m, "m" }; + if (v >= s) + return { v / s, "s" }; + if (v >= ms) + return { v / ms, "ms" }; + if (v >= us) + return { v / us, "us" }; + return { v / ns_1, "ns" }; +} + + +// ==================== 1. 锁死在 .cpp 内部的隐藏格式化器 ==================== +// 让 ServerLogger.cpp 内部的 fmt 彻底看懂 uns::LogArg 变体 +template <> +struct fmt::formatter +{ + constexpr auto parse(format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const uns::LogArg& la, FormatContext& ctx) const + { + return std::visit([&] (auto&& arg) -> decltype(ctx.out()) + { + using T = std::decay_t; + + // A. 针对容器的运行时展开 + if constexpr (std::is_same_v) + { + std::vector items; + // 【修正】去掉瞎编的 container_ptr,直接传入 items 容器供内部闭包填充 + arg.to_log_args(arg.ptr, items); + + fmt::format_to(ctx.out(), "["); + for (size_t i = 0; i < items.size(); ++i) + { + if (i > 0) + fmt::format_to(ctx.out(), ", "); + fmt::format_to(ctx.out(), fmt::runtime("{}"), items[i]); + } + return fmt::format_to(ctx.out(), "]"); + } + // B. 针对键值对的运行时展开 + else if constexpr (std::is_same_v) + { + std::vector items; + arg.to_log_args(arg.ptr, items); // 假设 PairCapturer 也是相同的解包逻辑 + if (items.size() >= 2) + fmt::format_to(ctx.out(), "{}: {}", items[0], items[1]); + return ctx.out(); + } + // C. 针对延迟执行函数的运行时展开 + else if constexpr (std::is_same_v) + { + // 【注意】请根据你 FuncCapturer 内部实际的求值函数名修改(如 .to_string() 或 .eval()) + return fmt::format_to(ctx.out(), "Function{{ptr: {}, type: {}}}", arg.code_ptr, arg.is_closure ? "Closure" : "PureFunc"); + } + // D. 时间相关的内容 + else if constexpr (std::is_same_v) + { + auto out = ctx.out(); + try + { + if (arg.mode == uns::TimeMode::Point) + { + std::tm tm = ToTm(arg.tp); + std::string s = FormatTime(tm, arg.format_spec); + return fmt::format_to(out, "{}", s); + } + else if (arg.mode == uns::TimeMode::TMPoint) + { + std::string s = FormatTime(arg._tm, arg.format_spec); + return fmt::format_to(out, "{}", s); + } + else + { + auto norm = NormalizeDuration(arg.duration); + return fmt::format_to(out, "{:.3f}{}", norm.value, norm.unit); + } + } + catch (...) + { + return fmt::format_to(out, "[time_error]"); + } + } + // E. 基础原生类型 + else + return fmt::format_to(ctx.out(), "{}", arg); + }, la.value); + } +}; + +std::string uns::toBinary(long number, int bits) +{ + bool negitive = (number < 0); + unsigned long positive = (negitive ? -number : number); + return (negitive ? "-" + toBinary(positive, bits) : toBinary(positive, bits)); +} + +std::string uns::toBinary(std::uint32_t number, int bits) +{ + return toBinary(static_cast(number), bits); +} + +std::string uns::toBinary(unsigned long number, int bits) +{ + std::string res; + while (true) + { + res += std::to_string(number % 2); + number = number / 2; + if (number == 0) + break; + } + std::reverse(res.begin(), res.end()); + while (res.size() < bits) + res = "0" + res; + return "0b" + res; +} + +std::string ServerLogger::GenerateLogHeader(uns::ServerLogLevel LogLevel) +{ + std::string hstr; + 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; + switch (LogLevel) + { + case uns::llTrace: + hstr += "[TRACE] "; + break; + case uns::llDebug: + hstr += "[DEBUG] "; + break; + case uns::llInfo: + hstr += "[INFO] "; + break; + case uns::llWarning: + hstr += "[WARNING] "; + break; + case uns::llError: + hstr += "[ERROR] "; + break; + case uns::llFatal: + hstr += "[FATAL] "; + break; + default: + hstr += "[UNKNOWN] "; + break; + } + return hstr; +} + +std::string ServerLogger::GenerateFileInfo(std::string filename, int line_num) +{ + // 使用 stringstream 替代 new/memset/sprintf + std::ostringstream oss; + oss << "(" << filename << " -> LINE=" << line_num << ") "; + return oss.str(); +} + +void ServerLogger::WriteBatchToOutputs(const std::deque& batch) +{ + if (batch.empty()) + return; + std::time_t now = std::time(nullptr); + for (const auto& item : batch) + { + if (item.empty()) + continue; + std::fwrite(item.c_str(), 1, item.size(), stdout); + if (LogStream.is_open()) + LogStream << item; + RotateIfNeeded(now, true); + } + std::fflush(stdout); + if (LogStream.is_open()) + LogStream.flush(); +} + +void ServerLogger::WorkerLoop() +{ + constexpr std::size_t kMaxBatchCount = 128; + + while (WorkerRunning) + { + std::deque local_batch; + { + std::unique_lock lock(QueueMutex); + QueueCV.wait(lock, [this] () + { + return !LogQueue.empty() || !WorkerRunning; + }); + if (!WorkerRunning && LogQueue.empty()) + break; + std::size_t count = 0; + while (!LogQueue.empty() && (count < kMaxBatchCount)) + { + local_batch.push_back(std::move(LogQueue.front())); + LogQueue.pop_front(); + count++; + } + } + if (!local_batch.empty()) + WriteBatchToOutputs(local_batch); + } + // 退出前把剩余日志尽量写完 + for (;;) + { + std::deque local_batch; + { + std::lock_guard lock(QueueMutex); + if (LogQueue.empty()) + break; + std::size_t count = 0; + while (!LogQueue.empty() && (count < kMaxBatchCount)) + { + local_batch.push_back(std::move(LogQueue.front())); + LogQueue.pop_front(); + count++; + } + } + WriteBatchToOutputs(local_batch); + } + ThreadRunningFlag = false; +} + +std::string ServerLogger::MakeRotatedFileName(const std::string& base, std::time_t t, int osl_index) +{ + tm* loctim = std::localtime(&t); + std::ostringstream oss; + oss << base; + if (RotatePeriod == uns::RP_Hourly) + oss << "_" << std::setw(4) << (loctim->tm_year + 1900) << std::setw(2) << std::setfill('0') << (loctim->tm_mon + 1) << std::setw(2) << std::setfill('0') << loctim->tm_mday << "_" << std::setw(2) << std::setfill('0') << loctim->tm_hour; + else if (RotatePeriod == uns::RP_Daily) + oss << "_" << std::setw(4) << (loctim->tm_year + 1900) << std::setw(2) << std::setfill('0') << (loctim->tm_mon + 1) << std::setw(2) << std::setfill('0') << loctim->tm_mday; + if (osl_index > 0) + oss << "_OSL" << osl_index; + oss << ".log"; + return oss.str(); +} + +void ServerLogger::RotateIfNeeded(std::time_t now, bool check_size_after_write) +{ + //控制台模式不进行轮转 + if (LogFileName.empty()) + return; + + // 时间轮转优先:若 period 发生变化则重建文件并重置 OSL 索引 + if (RotatePeriod != uns::RP_None) + { + std::time_t new_period_start = (RotatePeriod == uns::RP_Hourly ? (now / 3600) * 3600 : (now / 86400) * 86400); + if (new_period_start != CurrentFilePeriodStart) + { + // 时间轮转:关闭流并打开新的 period 文件,重置 OSL 索引 + if (LogStream.is_open()) + { + LogStream.flush(); + LogStream.close(); + } + CurrentOSLIndex = 0; + std::string actual_file = MakeRotatedFileName(LogFileName.empty() ? "UNSC" : LogFileName, now, CurrentOSLIndex); + LogStream.open(actual_file.c_str(), std::ios::out | std::ios::app); + CurrentFilePeriodStart = new_period_start; + // 完成时间轮转后不做 size 检查(新文件刚创建,肯定小于阈值) + return; + } + } + + // 如果要求检查大小(一般在写入之后调用),并且 MaxFileSizeBytes > 0,则进行大小轮转 + if (check_size_after_write && (MaxFileSizeBytes > 0) && LogStream.is_open()) + { + // 尝试使用 tellp 获取当前文件位置 + std::streampos pos = LogStream.tellp(); + std::size_t filesize = 0; + if (pos != static_cast(-1)) + filesize = static_cast(pos); + else + { + // 备用:通过打开文件获取大小 + // 构造当前文件名(基于当前 period 和索引) + std::string current_file = MakeRotatedFileName(LogFileName.empty() ? "UNSC" : LogFileName, now, CurrentOSLIndex); + std::ifstream ifs(current_file.c_str(), std::ios::binary | std::ios::ate); + if (ifs.is_open()) + { + filesize = static_cast(ifs.tellg()); + ifs.close(); + } + } + + if (filesize >= MaxFileSizeBytes) + { + // 增加 OSL 索引并打开新文件 + if (LogStream.is_open()) + { + LogStream.flush(); + LogStream.close(); + } + CurrentOSLIndex++; + std::string new_file = MakeRotatedFileName(LogFileName.empty() ? "UNSC" : LogFileName, now, CurrentOSLIndex); + LogStream.open(new_file.c_str(), std::ios::out | std::ios::app); + } + } +} + +inline std::string RewriteFormatString(const std::string& real_format, const uns::LogArg* args, size_t count, fmt::dynamic_format_arg_store& store) +{ + std::string out; + out.reserve(real_format.size()); + size_t arg_index = 0; + for (size_t i = 0; i < real_format.size();) + { + char c = real_format[i]; + // escaped {{ + if ((c == '{') && ((i + 1) < real_format.size()) && (real_format[i + 1] == '{')) + { + out += '{'; + i += 2; + continue; + } + // escaped }} + if ((c == '}') && ((i + 1) < real_format.size()) && (real_format[i + 1] == '}')) + { + out += '}'; + i += 2; + continue; + } + + if (c == '{') + { + size_t j = i + 1; + while ((j < real_format.size()) && (real_format[j] != '}')) + ++j; + if (j >= real_format.size()) + { + out += '{'; + ++i; + continue; + } + + std::string_view inside(real_format.data() + i + 1, j - i - 1); + if (arg_index < count) + { + uns::LogArg arg = args[arg_index]; + // 只处理 time spec + if (!inside.empty() && (inside[0] == ':')) + { + if (std::holds_alternative(arg.value)) + { + auto& tc = std::get(arg.value); + tc.format_spec = std::string(inside.substr(1)); + arg.value = tc; + store.push_back(arg); + } + else + store.push_back(args[arg_index]); + } + else + store.push_back(args[arg_index]); + } + + out += "{}"; + ++arg_index; + i = j + 1; + continue; + } + + out += c; + ++i; + } + + return out; +} + +void ServerLogger::LogImpl(uns::ServerLogLevel level, const std::string& format, const uns::LogArg* args, size_t count) +{ + std::string formatted; + try + { + if (count == 0) + formatted = format; + else + { + fmt::dynamic_format_arg_store> store; + for (size_t i = 0; i < count; ++i) + { + std::visit([&] (auto&& val) + { + using DeT = std::decay_t; + if constexpr (std::is_arithmetic_v || std::is_convertible_v || std::is_pointer_v) + store.push_back(val); + else // 【修正】这里必须传入 args[i](即 LogArg 本身) 这样才能正确触发上面我们写好的 fmt::formatter 路由 + store.push_back(fmt::format("{}", args[i])); + }, args[i].value); + } + // 【修正】显式提供模板参数 ,彻底解决 std::string 导致的推导失败 + formatted = fmt::vsprintf(format, store); + } + } + catch (const fmt::format_error& e) + { + formatted = std::string("<< log format error: ") + e.what() + " >> " + format; + } + + std::string logstr = GenerateLogHeader(level); + logstr += formatted; + logstr += "\n"; + + std::unique_lock lock(QueueMutex); + LogQueue.push_back(logstr); + lock.unlock(); + QueueCV.notify_one(); +} + +void ServerLogger::LogFImpl(uns::ServerLogLevel level, const std::string& filename, int line_num, const std::string& format, const uns::LogArg* args, size_t count) +{ + std::string formatted; + std::string real_format = GenerateFileInfo(filename, line_num) + format; + try + { + if (count == 0) + formatted = real_format; + else + { + fmt::dynamic_format_arg_store> store; + for (size_t i = 0; i < count; ++i) + { + std::visit([&] (auto&& val) + { + using DeT = std::decay_t; + if constexpr (std::is_arithmetic_v || std::is_convertible_v || std::is_pointer_v) + store.push_back(val); + else + store.push_back(fmt::format("{}", args[i])); + }, args[i].value); + } + // 【修正】显式提供模板参数 + formatted = fmt::vsprintf(real_format, store); + } + } + catch (const fmt::format_error& e) + { + formatted = std::string("<< log format error: ") + e.what() + " >> " + real_format; + } + + std::string logstr = GenerateLogHeader(level); + logstr += formatted; + logstr += "\n"; + + std::unique_lock lock(QueueMutex); + LogQueue.push_back(logstr); + lock.unlock(); + QueueCV.notify_one(); +} + +void ServerLogger::LogFMTImpl(uns::ServerLogLevel level, const std::string& format, const uns::LogArg* args, size_t count) +{ + std::string formatted; + try + { + if (count == 0) + formatted = format; + else + { + // 现代 {} 风格:动态包装擦除后的类型数组 + fmt::dynamic_format_arg_store store; + std::string rewrite_format = RewriteFormatString(format, args, count, store); + formatted = fmt::vformat(rewrite_format, store); + } + } + catch (const fmt::format_error& e) + { + formatted = std::string("<< log format error: ") + e.what() + " >> " + format; + } + + std::string logstr = GenerateLogHeader(level); + logstr += formatted; + logstr += "\n"; + + std::unique_lock lock(QueueMutex); + LogQueue.push_back(logstr); + lock.unlock(); + QueueCV.notify_one(); +} + +void ServerLogger::LogFMT_FImpl(uns::ServerLogLevel level, const std::string& filename, int line_num, const std::string& format, const uns::LogArg* args, size_t count) +{ + std::string formatted; + std::string real_format = GenerateFileInfo(filename, line_num) + format; + try + { + if (count == 0) + formatted = real_format; + else + { + fmt::dynamic_format_arg_store store; + std::string rewrite_format = RewriteFormatString(real_format, args, count, store); + formatted = fmt::vformat(rewrite_format, store); + } + } + catch (const fmt::format_error& e) + { + formatted = std::string("<< log format error: ") + e.what() + " >> " + real_format; + } + + std::string logstr = GenerateLogHeader(level); + logstr += formatted; + logstr += "\n"; + + std::unique_lock lock(QueueMutex); + LogQueue.push_back(logstr); + lock.unlock(); + QueueCV.notify_one(); +} + +ServerLogger::ServerLogger() +{ + //Default log off + //InitLogger(uns::llOff); + CurrentLevel = uns::llOff; + WorkerRunning = false; + RotatePeriod = uns::RP_None; + CurrentFilePeriodStart = 0; + MaxFileSizeBytes = 50 * 1024 * 1024; + CurrentOSLIndex = 0; + return; +} + +ServerLogger::ServerLogger(uns::ServerLogLevel log_level, std::string file, uns::LogRotationPeriod rotation, std::size_t max_bytes) +{ + InitLogger(log_level, file, rotation, max_bytes); + CurrentLevel = log_level; + return; +} + +ServerLogger::~ServerLogger() +{ + // 优雅关闭工作线程并刷新 + CloseLog(); + return; +} + +bool ServerLogger::InitLogger(uns::ServerLogLevel log_level, const std::string& file, uns::LogRotationPeriod rotation, std::size_t max_bytes) +{ + WorkerRunning = false; + RotatePeriod = rotation; + CurrentFilePeriodStart = 0; + CurrentLevel = log_level; + + CurrentOSLIndex = 0; + MaxFileSizeBytes = max_bytes; // 强制开启大小轮转,默认为 50MiB(可由调用者覆盖) + LogFileName = file; + + //初始化轮转 + if (!LogFileName.empty()) + { + time_t now = time(nullptr); + //取整点 + CurrentFilePeriodStart = (RotatePeriod == uns::RP_Hourly ? ((now / 3600) * 3600) : (RotatePeriod == uns::RP_Daily ? ((now / 86400) * 86400) : now)); + std::string actual_file = MakeRotatedFileName(LogFileName.empty() ? "UNSC" : LogFileName, now, CurrentOSLIndex); + LogStream.open(actual_file.c_str(), std::ios::out | std::ios::app); + + // 如果当前文件已超过大小限制,生成下一个 OSL 文件 + if (LogStream.is_open()) + { + // 尝试获取当前位置(文件大小),并在必要时轮转 + std::streampos pos = LogStream.tellp(); + std::size_t filesize = 0; + if (pos != static_cast(-1)) + filesize = static_cast(pos); + else + { + // 备用方案:用 ifstream 直接获取文件大小 + std::ifstream ifs(actual_file.c_str(), std::ios::binary | std::ios::ate); + if (ifs.is_open()) + { + filesize = static_cast(ifs.tellg()); + ifs.close(); + } + } + if (filesize >= MaxFileSizeBytes) + { + // increase osl index and open new file + LogStream.close(); + CurrentOSLIndex = 1; + actual_file = MakeRotatedFileName(LogFileName.empty() ? "UNSC" : LogFileName, now, CurrentOSLIndex); + LogStream.open(actual_file.c_str(), std::ios::out | std::ios::app); + } + } + } + + //启动日志线程 + bool started = true; + if (!WorkerRunning) + { + WorkerRunning = true; + try + { + WorkerThread = std::thread(&ServerLogger::WorkerLoop, this); + } + catch (...) + { + WorkerRunning = false; + started = false; + } + } + return ((LogStream.is_open() || LogFileName.empty()) && started); +} + +void ServerLogger::CloseLog() +{ + //关闭工作线程 + WorkerRunning = false; + QueueCV.notify_all(); + if (WorkerThread.joinable()) + WorkerThread.join(); + while (ThreadRunningFlag) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + //关闭文件流 + if (LogStream.is_open()) + LogStream.close(); + CurrentLevel = uns::llOff; +} + +void ServerLogger::DisableLog() +{ + CurrentLevel = uns::llOff; +} + +void ServerLogger::FlushLogBuffer() +{ + //触发写入 + QueueCV.notify_all(); + //等待队列清空 + while (true) + { + std::unique_lock lock(QueueMutex); + if (LogQueue.empty()) + break; + lock.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + if (LogStream.is_open()) + LogStream.flush(); +} + +void ServerLogger::EnableLog(uns::ServerLogLevel level) +{ + CurrentLevel = (level == uns::llOff ? uns::llAll : level); +} + +ServerLogger GlobalServerLogger; \ No newline at end of file diff --git a/UNSWebServerCore/ServerLogger.h b/UNSWebServerCore/ServerLogger.h new file mode 100644 index 0000000..6fc351c --- /dev/null +++ b/UNSWebServerCore/ServerLogger.h @@ -0,0 +1,210 @@ +#pragma once +#pragma warning(disable : 4996) +#include +#include +#include +#include +#include +#include +#include +#include "LogArg.h" +#include "Export.h" +#include + +namespace uns +{ + enum ServerLogLevel + { + llAll = 0x000000, + llTrace = 0x000001, + llDebug = 0x000010, + llInfo = 0x000020, + llWarning = 0x000030, + llError = 0x000040, + llFatal = 0x000050, + llOff = 0xFFFFFF + }; + + enum LogRotationPeriod + { + RP_None = 0, + RP_Hourly = 1, + RP_Daily = 2 + }; + + std::string UNSWSC_DLL_EXPORT toBinary(long number, int bits); + std::string UNSWSC_DLL_EXPORT toBinary(std::uint32_t number, int bits); + std::string UNSWSC_DLL_EXPORT toBinary(unsigned long number, int bits); +}; + +class UNSWSC_DLL_EXPORT ServerLogger +{ +private: + std::fstream LogStream; + std::string LogFileName; + std::atomic CurrentLevel; //避免数据竟态UB + + // 异步队列与线程控制 + std::deque LogQueue; + std::mutex QueueMutex; + std::condition_variable QueueCV; + std::thread WorkerThread; + std::atomic_bool WorkerRunning; + std::atomic_bool ThreadRunningFlag; + + // 日志分时段(轮转)支持 + uns::LogRotationPeriod RotatePeriod; + std::time_t CurrentFilePeriodStart; + + // 新增:大小轮转支持 + std::size_t MaxFileSizeBytes; // 最大文件大小(字节) + int CurrentOSLIndex; // 当前 period 下的 OSL 索引(从 0 开始) + +private: + std::string GenerateLogHeader(uns::ServerLogLevel LogLevel); + std::string GenerateFileInfo(std::string filename, int line_num); + void WriteBatchToOutputs(const std::deque& batch) +; + void WorkerLoop(); + std::string MakeRotatedFileName(const std::string& base, std::time_t t, int osl_index = 0); + void RotateIfNeeded(std::time_t now, bool check_size_after_write = false); + + void LogImpl(uns::ServerLogLevel level, const std::string& format, const uns::LogArg* args, size_t count); + void LogFImpl(uns::ServerLogLevel level, const std::string& filename, int line_num, const std::string& format, const uns::LogArg* args, size_t count); + void LogFMTImpl(uns::ServerLogLevel level, const std::string& format, const uns::LogArg* args, size_t count); + void LogFMT_FImpl(uns::ServerLogLevel level, const std::string& filename, int line_num, const std::string& format, const uns::LogArg* args, size_t count); + +public: + ServerLogger(); + ServerLogger(uns::ServerLogLevel log_level, std::string file = "", uns::LogRotationPeriod rotation = uns::RP_None, std::size_t max_bytes = 50 * 1024 * 1024); + ServerLogger(const ServerLogger& obj) = delete; + ~ServerLogger(); + +public: + bool InitLogger(uns::ServerLogLevel log_level, const std::string& file = "", uns::LogRotationPeriod rotation = uns::RP_None, std::size_t max_bytes = 50 * 1024 * 1024); + void CloseLog(); + void DisableLog(); + void FlushLogBuffer(); + void EnableLog(uns::ServerLogLevel level); + + template + void Log(uns::ServerLogLevel level, const std::string& format, Args&&... args); + template + void LogF(uns::ServerLogLevel level, const std::string& filename, int line_num, const std::string format, Args&&... args); + template + void LogFMT(uns::ServerLogLevel level, const std::string& format, Args&&... args); + template + void LogFMT_F(uns::ServerLogLevel level, const std::string& filename, int line_num, const std::string format, Args&&... args); +}; + +template +inline void ServerLogger::Log(uns::ServerLogLevel level, const std::string& format, Args&&... args) +{ + uns::ServerLogLevel curr_level = CurrentLevel.load(std::memory_order_relaxed); + if ((curr_level == uns::llOff) || (level < curr_level)) + return; + + if constexpr (sizeof...(Args) == 0) + LogImpl(level, format, nullptr, 0); + else + { + // 在客户端的栈上瞬间分配数组并完成类型擦除 + uns::LogArg packed_args[] = { uns::LogArg(std::forward(args))... }; + LogImpl(level, format, packed_args, sizeof...(Args)); + } +} + +template +inline void ServerLogger::LogF(uns::ServerLogLevel level, const std::string& filename, int line_num, std::string format, Args&&... args) +{ + uns::ServerLogLevel curr_level = CurrentLevel.load(std::memory_order_relaxed); + if ((curr_level == uns::llOff) || (level < curr_level)) + return; + + if constexpr (sizeof...(Args) == 0) + LogFImpl(level, filename, line_num, format, nullptr, 0); + else + { + uns::LogArg packed_args[] = { uns::LogArg(std::forward(args))... }; + LogFImpl(level, filename, line_num, format, packed_args, sizeof...(Args)); + } +} + +template +inline void ServerLogger::LogFMT(uns::ServerLogLevel level, const std::string& format, Args && ...args) +{ + uns::ServerLogLevel curr_level = CurrentLevel.load(std::memory_order_relaxed); + if ((curr_level == uns::llOff) || (level < curr_level)) + return; + + if constexpr (sizeof...(Args) == 0) + LogFMTImpl(level, format, nullptr, 0); + else + { + uns::LogArg packed_args[] = { uns::LogArg(std::forward(args))... }; + LogFMTImpl(level, format, packed_args, sizeof...(Args)); + } +} + +template +inline void ServerLogger::LogFMT_F(uns::ServerLogLevel level, const std::string& filename, int line_num, const std::string format, Args && ...args) +{ + uns::ServerLogLevel curr_level = CurrentLevel.load(std::memory_order_relaxed); + if ((curr_level == uns::llOff) || (level < curr_level)) + return; + + if constexpr (sizeof...(Args) == 0) + LogFMT_FImpl(level, filename, line_num, format, nullptr, 0); + else + { + uns::LogArg packed_args[] = { uns::LogArg(std::forward(args))... }; + LogFMT_FImpl(level, filename, line_num, format, packed_args, sizeof...(Args)); + } +} + +extern UNSWSC_DLL_EXPORT ServerLogger GlobalServerLogger; + +#if (defined(_WIN32) || defined(_WIN64)) +#define __FILENAME__ std::strrchr("\\" __FILE__, '\\') + 1 +#else +#define __FILENAME__ std::strrchr("/" __FILE__, '/') + 1 +#endif + +#define SCLOG_CONSOLE_INIT(__log_level__) GlobalServerLogger.InitLogger(__log_level__) +#define SCLOG_FILE_INIT(__file_name__, __log_level__, ...) GlobalServerLogger.InitLogger(__log_level__, __file_name__, ##__VA_ARGS__) + +#define SCLOG_WRITE(__level__, __text__, ...) GlobalServerLogger.LogF(__level__, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOG_TRACE(__text__, ...) GlobalServerLogger.LogF(uns::llTrace, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOG_DEBUG(__text__, ...) GlobalServerLogger.LogF(uns::llDebug, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOG_INFO(__text__, ...) GlobalServerLogger.LogF(uns::llInfo, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOG_WARNING(__text__, ...) GlobalServerLogger.LogF(uns::llWarning, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOG_ERROR(__text__, ...) GlobalServerLogger.LogF(uns::llError, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOG_FATAL(__text__, ...) GlobalServerLogger.LogF(uns::llFatal, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) + +#define SCLOG_SHORT_WRITE(__level__, __text__, ...) GlobalServerLogger.Log(__level__, __text__, ##__VA_ARGS__) +#define SCLOG_SHORT_TRACE(__text__, ...) GlobalServerLogger.Log(uns::llTrace, __text__, ##__VA_ARGS__) +#define SCLOG_SHORT_DEBUG(__text__, ...) GlobalServerLogger.Log(uns::llDebug, __text__, ##__VA_ARGS__) +#define SCLOG_SHORT_INFO(__text__, ...) GlobalServerLogger.Log(uns::llInfo, __text__, ##__VA_ARGS__) +#define SCLOG_SHORT_WARNING(__text__, ...) GlobalServerLogger.Log(uns::llWarning, __text__, ##__VA_ARGS__) +#define SCLOG_SHORT_ERROR(__text__, ...) GlobalServerLogger.Log(uns::llError, __text__, ##__VA_ARGS__) +#define SCLOG_SHORT_FATAL(__text__, ...) GlobalServerLogger.Log(uns::llFatal, __text__, ##__VA_ARGS__) + +#define SCLOGF_WRITE(__level__, __text__, ...) GlobalServerLogger.LogFMT_F(__level__, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOGF_TRACE(__text__, ...) GlobalServerLogger.LogFMT_F(uns::llTrace, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOGF_DEBUG(__text__, ...) GlobalServerLogger.LogFMT_F(uns::llDebug, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOGF_INFO(__text__, ...) GlobalServerLogger.LogFMT_F(uns::llInfo, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOGF_WARNING(__text__, ...) GlobalServerLogger.LogFMT_F(uns::llWarning, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOGF_ERROR(__text__, ...) GlobalServerLogger.LogFMT_F(uns::llError, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) +#define SCLOGF_FATAL(__text__, ...) GlobalServerLogger.LogFMT_F(uns::llFatal, __FILENAME__, __LINE__, __text__, ##__VA_ARGS__) + +#define SCLOGF_SHORT_WRITE(__level__, __text__, ...) GlobalServerLogger.LogFMT(__level__, __text__, ##__VA_ARGS__) +#define SCLOGF_SHORT_TRACE(__text__, ...) GlobalServerLogger.LogFMT(uns::llTrace, __text__, ##__VA_ARGS__) +#define SCLOGF_SHORT_DEBUG(__text__, ...) GlobalServerLogger.LogFMT(uns::llDebug, __text__, ##__VA_ARGS__) +#define SCLOGF_SHORT_INFO(__text__, ...) GlobalServerLogger.LogFMT(uns::llInfo, __text__, ##__VA_ARGS__) +#define SCLOGF_SHORT_WARNING(__text__, ...) GlobalServerLogger.LogFMT(uns::llWarning, __text__, ##__VA_ARGS__) +#define SCLOGF_SHORT_ERROR(__text__, ...) GlobalServerLogger.LogFMT(uns::llError, __text__, ##__VA_ARGS__) +#define SCLOGF_SHORT_FATAL(__text__, ...) GlobalServerLogger.LogFMT(uns::llFatal, __text__, ##__VA_ARGS__) + +#define SCLOG_ENABLE(__level__) GlobalServerLogger.EnableLog(__level__) +#define SCLOG_DISABLE() GlobalServerLogger.DisableLog() +#define SCLOG_CLOSE() GlobalServerLogger.CloseLog() \ No newline at end of file diff --git a/UNSWebServerCore/ServerProcessor.cpp b/UNSWebServerCore/ServerProcessor.cpp new file mode 100644 index 0000000..35da586 --- /dev/null +++ b/UNSWebServerCore/ServerProcessor.cpp @@ -0,0 +1,200 @@ +#include "ServerProcessor.h" +#include "ServerLogger.h" +#include "PathTraversal.h" +#include "HTTPObjectsBridge.h" +#include "UNSResponseBuilder.h" + +#include +#include +#include +#include +#ifdef __GNUG__ +#include +#include +#endif + +// demangle(GNU)/回退(其他编译器) +static std::string demangle_name(const char* name) +{ +#ifdef __GNUG__ + int status = 0; + char *dem = abi::__cxa_demangle(name, nullptr, nullptr, &status); + std::string ret = (status == 0 && dem) ? dem : name; + std::free(dem); + return ret; +#else + return name; +#endif +} + +// RTTI 名称缓存(线程安全) +static const std::string& RTTISubClassName(const std::type_info &ti) +{ + using key_t = std::type_index; + static std::mutex m; + static std::unordered_map cache; + key_t k(ti); + + // 快速检查(持锁短时间) + { + std::lock_guard g(m); + auto it = cache.find(k); + if (it != cache.end()) + return it->second; + } + + // 若未缓存,先在无锁区做 demangle(可能较慢),然后再插入缓存 + std::string dem = demangle_name(ti.name()); + + { + std::lock_guard g(m); + auto [it, inserted] = cache.emplace(k, std::move(dem)); + return it->second; + } +} + +class ServerProcessor::Impl +{ +public: + bool IPCheck = false; + IPTablePtr BlockedIPs = nullptr; + std::map StreamInfo; +}; + +ServerProcessor::ServerProcessor() : pimpl(std::make_unique()) +{ +} + +ServerProcessor::~ServerProcessor() = default; + +void ServerProcessor::EnableIPCheck() +{ + pimpl->IPCheck = true; + return; +} + +void ServerProcessor::DisableIPCheck() +{ + pimpl->IPCheck = false; + return; +} + +void ServerProcessor::UpdateBlockedIPList(IPTablePtr ips) +{ + pimpl->BlockedIPs = ips; + return; +} + +void ServerProcessor::AppenedBlockedIP(DateTime::Span block_time, std::string ip) +{ + DateTime expr_time = (DateTime::Now() += block_time); + IPList li{ ip }; + pimpl->BlockedIPs->Appened(expr_time, li); + pimpl->BlockedIPs->Update(); + SCLOG_INFO("IP: [%s] has been blocked untill {%s}", ip.c_str(), std::string(expr_time).c_str()); + return; +} + +uns::HTTPMethod ServerProcessor::GetMethod(uns::RequestPtr request) +{ + std::string method = request->GetImpl()->webcc_req->method(); + if (method == "GET") + return uns::HTTPMethod::H_GET; + else if (method == "PUT") + return uns::HTTPMethod::H_PUT; + else if (method == "POST") + return uns::HTTPMethod::H_POST; + else if (method == "HEAD") + return uns::HTTPMethod::H_HEAD; + else if (method == "TRACE") + return uns::HTTPMethod::H_TRACE; + else if (method == "PATCH") + return uns::HTTPMethod::H_PATCH; + else if (method == "DELETE") + return uns::HTTPMethod::H_DELETE; + else if (method == "OPTIONS") + return uns::HTTPMethod::H_OPTIONS; + else if (method == "CONNECT") + return uns::HTTPMethod::H_CONNECT; + else + return uns::HTTPMethod::H_UNKNOWN; +} + +void ServerProcessor::AddStreamSettings(std::string method, bool stream) +{ + pimpl->StreamInfo.insert(std::pair(method, stream)); + return; +} + +uns::PathTraversalDefenceLevel ServerProcessor::PTDefence() +{ + return uns::PathTraversalDefenceLevel::DenyAll; +} + +bool ServerProcessor::IsPathSafe(const std::string& raw_path) +{ + return false; +} + +uns::ResponsePtr ServerProcessor::Handle(uns::RequestPtr request) +{ + std::string x_real_ip; + if(request->GetImpl()->webcc_req->HasHeader("X-Real-IP")) + x_real_ip = request->GetImpl()->webcc_req->GetHeader("X-Real-IP"); + std::string req_ip = (x_real_ip.empty() ? request->GetImpl()->webcc_req->address() : x_real_ip); + SCLOGF_INFO("Request recived by {{{}}}, ip: [{}], method: {}, URL: {}", RTTISubClassName(typeid(*this)), req_ip, request->GetImpl()->webcc_req->method(), request->GetImpl()->webcc_req->url().path()); + if (pimpl->IPCheck && (pimpl->BlockedIPs != nullptr)) + { + pimpl->BlockedIPs->Update(); + if (pimpl->BlockedIPs->IPExist(req_ip)) + return uns::ResponseBuilder().IPBlocked()(); + } + // path test + std::string path = request->GetImpl()->webcc_req->url().path(); + auto status = PathTraversal::AnalyzeUrlTraversal(path); + if(status != PathTraversal::UrlSafetyStatus::Safe) + SCLOGF_WARNING("PathTraversal Detected: {}, Level: {}", path, PathTraversal::ToString(status)); + switch(PTDefence()) + { + case uns::PathTraversalDefenceLevel::DenyAll: + if(status != PathTraversal::UrlSafetyStatus::Safe) + return uns::ResponseBuilder().Forbidden()(); + break; + case uns::PathTraversalDefenceLevel::AutoNormalize: + { + if(status == PathTraversal::UrlSafetyStatus::EvasiveTraversal) + return uns::ResponseBuilder().Forbidden()(); + auto decoded_path = PathTraversal::UrlDecode(path); + if(!IsPathSafe(decoded_path)) + return uns::ResponseBuilder().Forbidden()(); + auto url = request->GetImpl()->webcc_req->url(); + url.ForceSet_Path(PathTraversal::NormalizeUrlPath(decoded_path)); + request->GetImpl()->webcc_req->set_url(std::move(url)); + break; + } + case uns::PathTraversalDefenceLevel::AllowNormal: + { + if(status == PathTraversal::UrlSafetyStatus::EvasiveTraversal) + return uns::ResponseBuilder().Forbidden()(); + auto decoded_path = PathTraversal::UrlDecode(path); + if(!IsPathSafe(decoded_path)) + return uns::ResponseBuilder().Forbidden()(); + auto url = request->GetImpl()->webcc_req->url(); + url.ForceSet_Path(decoded_path); + request->GetImpl()->webcc_req->set_url(std::move(url)); + break; + } + default: + break; //Check Bypassed by [AllowAll] + } + uns::ResponsePtr ptr = Processor(request); + return ptr; +} + +bool ServerProcessor::Stream(const std::string& method) +{ + if (pimpl->StreamInfo.find(method) != pimpl->StreamInfo.end()) + return pimpl->StreamInfo.at(method); + else + return false; +} diff --git a/UNSWebServerCore/ServerProcessor.h b/UNSWebServerCore/ServerProcessor.h new file mode 100644 index 0000000..96f7a4e --- /dev/null +++ b/UNSWebServerCore/ServerProcessor.h @@ -0,0 +1,35 @@ +#pragma once +#include "Global.h" +#include "Export.h" +#include "IPTable.h" +#include "HTTPObjects.h" + +class UNSWSC_DLL_EXPORT ServerProcessor +{ +protected: + class Impl; + std::unique_ptr pimpl; + +public: + ServerProcessor(); + virtual ~ServerProcessor(); + +public: + void EnableIPCheck(); + void DisableIPCheck(); + void UpdateBlockedIPList(IPTablePtr ips); + void AppenedBlockedIP(DateTime::Span block_time, std::string ip); + uns::HTTPMethod GetMethod(uns::RequestPtr request); + void AddStreamSettings(std::string method, bool stream); + +public: + virtual uns::ResponsePtr Processor(uns::RequestPtr request) = 0; + virtual uns::PathTraversalDefenceLevel PTDefence(); + virtual bool IsPathSafe(const std::string& raw_path); + +public: + uns::ResponsePtr Handle(uns::RequestPtr request); + bool Stream(const std::string& method); +}; + +using ServerProcessorPtr = std::shared_ptr; \ No newline at end of file diff --git a/UNSWebServerCore/SessionManager.cpp b/UNSWebServerCore/SessionManager.cpp new file mode 100644 index 0000000..b374b70 --- /dev/null +++ b/UNSWebServerCore/SessionManager.cpp @@ -0,0 +1,372 @@ +#include "SessionManager.h" + +#include +#include "SafeRNG.h" +#ifndef _WIN32 +#include "UOHash.h" +#else +#include "../UOHash/UOHash.h" +#endif +#include +#include +#include + +const std::chrono::hours SessionManager::Session::max_age = std::chrono::hours(24 * 5); + +SessionManager::Session::Session() //默认构造函数:无效的已过期空Cookie +{ + uid = -1; + expiry_date = std::chrono::system_clock::now() - std::chrono::seconds(1); +} + +SessionManager::Session::Session(const Session& obj) +{ + uid = obj.uid; + cookie = obj.cookie; + expiry_date = obj.expiry_date; +} + +SessionManager::Session::Session(const std::string& cookie) +{ + uid = -1; + this->cookie = cookie; + expiry_date = std::chrono::system_clock::now() - std::chrono::seconds(1); +} + +SessionManager::Session::Session(const Json::Value& json_obj) +{ + if(json_obj["UID"].isInt() && json_obj["Cookie"].isString() && (json_obj["ExpiryDate"].isInt64())) + { + uid = json_obj["UID"].asInt(); + cookie = json_obj["Cookie"].asString(); + auto ms = std::chrono::milliseconds(json_obj["ExpiryDate"].asInt64()); + expiry_date = systime(ms); + } + else + { + uid = -1; + expiry_date = std::chrono::system_clock::now() - std::chrono::seconds(1); + } +} + +SessionManager::Session::Session(int uid, const std::string& cookie) +{ + this->uid = uid; + this->cookie = cookie; + expiry_date = std::chrono::system_clock::now() + max_age; +} + +int SessionManager::Session::GetUID() const +{ + return uid; +} + +bool SessionManager::Session::Expired() const +{ + return (expiry_date < std::chrono::system_clock::now()); +} + +std::string SessionManager::Session::GetCookie() const +{ + return cookie; +} + +SessionManager::systime SessionManager::Session::GetExpiryDate() const +{ + return expiry_date; +} + +bool SessionManager::Session::NeedExpiryDateRefresh() const +{ + if (Expired()) + return true; + auto usage_time = std::chrono::system_clock::now() - (expiry_date - max_age); + return (usage_time >= std::chrono::hours(24)); +} + +void SessionManager::Session::SetUID(int uid) +{ + this->uid = uid; +} + +void SessionManager::Session::RefreshExpiryDate() +{ + expiry_date = std::chrono::system_clock::now() + max_age; +} + +void SessionManager::Session::SetCookie(const std::string& cookie) +{ + this->cookie = cookie; +} + +void SessionManager::Session::SetExpiryDate(const systime& expiry_time) +{ + this->expiry_date = expiry_date; +} + +bool SessionManager::Session::operator<(const Session& obj) const +{ + return (this->cookie < obj.cookie); +} + +SessionManager::Session::operator Json::Value() const +{ + Json::Value root; + root["UID"] = uid; + root["Cookie"] = cookie; + auto ms = std::chrono::duration_cast(expiry_date.time_since_epoch()).count(); + root["ExpiryDate"] = Json::Int64(ms); + return root; +} + +int SessionManager::Session::GetMaxAgeSeconds() +{ + return (max_age.count() * 3600); +} + +SessionManager::SessionManager() +{ +} + +std::string SessionManager::Random() +{ + return RandomNumberGenerator::SecureRandomHex(32); +} + +std::string SessionManager::ISO8601_TimeString() +{ + time_t now = time(nullptr); + tm tim = {}; +#if defined(_WIN32) + localtime_s(&tim, &now); // Windows +#else + localtime_r(&now, &tim); // POSIX +#endif + return fmt::format("{}-{}-{}T{}:{}:{}+08:00", (tim.tm_year + 1900), (tim.tm_mon + 1), tim.tm_mday, tim.tm_hour, tim.tm_min, tim.tm_sec); +} + +void SessionManager::AutoCleanCookiePool() +{ + std::lock_guard lock(pool_mutex); //自动清理前需要上锁 + for (auto it = cookie_storage.begin(); it != cookie_storage.end(); ) //正向遍历,删除无效/过期Session + { + const std::string& cookie = it->first; + int uid = it->second; + + auto rit = reverse_cookie_storage.find(uid); + if (rit == reverse_cookie_storage.end()) //有Cookie无Session,删掉 + { + it = cookie_storage.erase(it); + continue; + } + + Session index_session(cookie.substr(prefix.length())); + auto& rcs = rit->second; + auto sit = rcs.find(index_session); + if ((sit == rcs.end()) || sit->Expired()) //无Session或Session已过期,删掉 + { + if (sit != rcs.end()) + rcs.erase(sit); + if (rcs.empty()) + reverse_cookie_storage.erase(rit); + it = cookie_storage.erase(it); + continue; + } + ++it; //前置++性能开销较低 + } + + for (auto rit = reverse_cookie_storage.begin(); rit != reverse_cookie_storage.end(); ) //反向遍历,删除异常Session + { + auto& rcs = rit->second; + for (auto sit = rcs.begin(); sit != rcs.end(); ) + { + std::string key = prefix + sit->GetCookie(); + if (cookie_storage.find(key) == cookie_storage.end()) //正向没有反向有,异常Session(无法被利用) + { + auto sit_next = std::next(sit); + rcs.erase(sit); + sit = sit_next; + } + else + ++sit; + } + if (rcs.empty()) + { + auto rit_next = std::next(rit); + reverse_cookie_storage.erase(rit); + rit = rit_next; + } + else + ++rit; + } +} + +std::string SessionManager::GenerateCookieForUser(int uid, bool cookie_only) +{ + //生成Cookie和Session + std::string str_uid = std::to_string(uid); + std::string raw_cookie = str_uid + ISO8601_TimeString() + str_uid + Random() + str_uid; + auto res = uns::UOHash::HashString(uns::HashID::SHA3_224, raw_cookie); + if (!res) + return ""; + std::string cookie = res.GetResult(); + Session session(uid, cookie); + //存储到Cookie池 - 使用互斥体保证线程安全 + { + std::lock_guard lock(pool_mutex); + cookie_storage.insert({ (prefix + cookie), uid }); + if (reverse_cookie_storage.find(uid) == reverse_cookie_storage.end()) + reverse_cookie_storage.insert({ uid, { session } }); + else + reverse_cookie_storage.at(uid).insert(session); + } + //返回用于响应的Cookie串 + if(cookie_only) + return (prefix + cookie); + else + return fmt::format(fmt::runtime(cookie_template), cookie, Session::GetMaxAgeSeconds()); +} + +std::string SessionManager::DeleteCookie(const std::string& cookie) +{ + std::lock_guard lock(pool_mutex); + if (cookie_storage.find(cookie) != cookie_storage.end()) + { + int uid = cookie_storage.at(cookie); + cookie_storage.erase(cookie); + if (reverse_cookie_storage.find(uid) != reverse_cookie_storage.end()) + { + auto& rcs = reverse_cookie_storage.at(uid); + Session index_session(cookie.substr(prefix.length())); + rcs.erase(index_session); + if (rcs.empty()) + reverse_cookie_storage.erase(uid); + } + } + //返回用于响应的Cookie串(注销浏览器端的Cookie) + return fmt::format(fmt::runtime(cookie_template.substr(prefix.length())), cookie, 0); +} + +bool SessionManager::CheckRequestCookie(uns::RequestPtr request, int& uid) +{ + uid = -1; + if (!request->HasHeader("Cookie")) //未找到Cookie + return false; + std::string cookie = request->GetHeader("Cookie"); + std::lock_guard lock(pool_mutex); //使用互斥体保证Cookie池的线程安全 + auto cit = cookie_storage.find(cookie); + if (cit == cookie_storage.end()) //不正确的Cookie + return false; + uid = cit->second; + if (reverse_cookie_storage.find(uid) == reverse_cookie_storage.end()) + return false; //理论上不会出现有Cookie没有UID的情况,仅作兜底处理 + Session index_session(cookie.substr(prefix.length())); + auto& rcs = reverse_cookie_storage.at(uid); + auto it = rcs.find(index_session); + if (it == rcs.end()) //无有效Session + return false; + if (it->Expired()) //Cookie过期,删除该Session及对应的cookie。 + { + rcs.erase(it); //删除Session + if (rcs.empty()) //如果一个用户没有任何有效的Cookie,删除该用户的记录 + reverse_cookie_storage.erase(uid); + cookie_storage.erase(cookie); //删除Cookie + return false; + } + if (it->NeedExpiryDateRefresh()) //根据需要决定是否刷新Cookie + { + auto session = rcs.extract(it); + session.value().RefreshExpiryDate(); + rcs.insert(std::move(session)); + } + return true; //到达此处意味着找到有效的Cookie,并已完成必要的更新工作 +} + +std::string SessionManager::DeleteAllCookieForUser(int uid, const std::string & current_cookie) +{ + std::lock_guard lock(pool_mutex); + if (reverse_cookie_storage.find(uid) != reverse_cookie_storage.end()) + { + auto sessions = reverse_cookie_storage.at(uid); + reverse_cookie_storage.erase(uid); + for(const auto& session : sessions) + { + if(cookie_storage.find(session.GetCookie()) != cookie_storage.end()) + cookie_storage.erase(session.GetCookie()); + } + } + //返回用于响应的Cookie串(注销浏览器端的Cookie) + return fmt::format(fmt::runtime(cookie_template.substr(prefix.length())), current_cookie, 0); +} + +bool SessionManager::HasDumpedCookie(const std::string& path) +{ + namespace fs = std::filesystem; + fs::path fn = (fs::path(path) / "unsc_sessions.json"); + std::error_code ec; + return fs::is_regular_file(fn, ec); +} + +bool SessionManager::LoadDumpedCookie(const std::string& path) +{ + namespace fs = std::filesystem; + fs::path fn = (fs::path(path) / "unsc_sessions.json"); + std::fstream fin(fn.string(), std::ios::in); + if(!fin.is_open()) + return false; + try + { + Json::Reader reader; + Json::Value cookies; + if(!reader.parse(fin, cookies, false)) + { + fin.close(); + return false; + } + fin.close(); + if(!cookies.isArray()) + return false; + std::lock_guard lock(pool_mutex); //使用互斥体保证Cookie池的线程安全 + for(const auto& cookie : cookies) + { + Session session(cookie); + if(session.Expired()) + continue; + int uid = session.GetUID(); + cookie_storage.insert({ (prefix + session.GetCookie()), uid }); + if (reverse_cookie_storage.find(uid) == reverse_cookie_storage.end()) + reverse_cookie_storage.insert({ uid, { session } }); + else + reverse_cookie_storage.at(uid).insert(session); + } + std::error_code ec; + fs::remove(fn, ec); + return true; + } + catch(...) + { + return false; + } +} + +bool SessionManager::DumpAllValidCookies(const std::string& path) +{ + namespace fs = std::filesystem; + fs::path fn = (fs::path(path) / "unsc_sessions.json"); + std::fstream fout(fn.string(), std::ios::out | std::ios::trunc); + if(!fout.is_open()) + return false; + Json::Value cookies(Json::arrayValue); + { + std::lock_guard lock(pool_mutex); //使用互斥体保证Cookie池的线程安全 + for(const auto& [uid, sessions] : reverse_cookie_storage) + for(const auto& session : sessions) + cookies.append(session); + } + Json::FastWriter writer; + fout << writer.write(cookies) << std::endl; + fout.close(); + return true; +} + +SessionManager GlobalSessionManager; diff --git a/UNSWebServerCore/SessionManager.h b/UNSWebServerCore/SessionManager.h new file mode 100644 index 0000000..175abc8 --- /dev/null +++ b/UNSWebServerCore/SessionManager.h @@ -0,0 +1,86 @@ +#pragma once +#include +#include +#include +#include +#include "Export.h" +#include "HTTPObjects.h" + +namespace Json +{ + class Value; +} + +class UNSWSC_DLL_EXPORT SessionManager +{ +public: + using systime = std::chrono::system_clock::time_point; + + class UNSWSC_DLL_EXPORT Session + { + private: + int uid; + std::string cookie; + systime expiry_date; + + static const std::chrono::hours max_age; + + public: + Session(); + Session(const Session& obj); + Session(const std::string& cookie); //构造仅用于比较的临时Session对象 + Session(const Json::Value& json_obj); //从JSON反序列化 + Session(int uid, const std::string& cookie); + + public: + int GetUID() const; + bool Expired() const; + std::string GetCookie() const; + systime GetExpiryDate() const; + bool NeedExpiryDateRefresh() const; //使用超过一天且在活跃的cookie将被刷新。此处仅作时间判断 + + public: + void SetUID(int uid); + void RefreshExpiryDate(); + void SetCookie(const std::string& cookie); + void SetExpiryDate(const systime& expiry_time); + + public: + bool operator<(const Session& obj) const; //For std::map/std::set/... + operator Json::Value() const; //JSON序列化 + + static int GetMaxAgeSeconds(); + }; + +private: + std::mutex pool_mutex; + //Key: Cookie Pair(oreo=xxxxx), Value: UID + std::map cookie_storage; + //Key: UID, Value: Cookie Lists + std::map> reverse_cookie_storage; + + const std::string prefix = "oreo="; + //const std::string cookie_template = prefix + "{}; HttpOnly; Path=/; SameSite=Lax; Max-Age={}"; + const std::string cookie_template = prefix + "{}; HttpOnly; Secure; Path=/; SameSite=None; Max-Age={}"; + +public: + SessionManager(); + SessionManager(const SessionManager& obj) = delete; + +public: + static std::string Random(); + static std::string ISO8601_TimeString(); + +public: + void AutoCleanCookiePool(); + std::string GenerateCookieForUser(int uid, bool cookie_only = false); + std::string DeleteCookie(const std::string& cookie); + bool CheckRequestCookie(uns::RequestPtr request, int& uid); + std::string DeleteAllCookieForUser(int uid, const std::string& current_cookie); + + bool HasDumpedCookie(const std::string& path); + bool LoadDumpedCookie(const std::string& path); + bool DumpAllValidCookies(const std::string& path); +}; + +extern UNSWSC_DLL_EXPORT SessionManager GlobalSessionManager; diff --git a/UNSWebServerCore/SyncFileReceiver.cpp b/UNSWebServerCore/SyncFileReceiver.cpp new file mode 100644 index 0000000..5a64e8d --- /dev/null +++ b/UNSWebServerCore/SyncFileReceiver.cpp @@ -0,0 +1,255 @@ +#include "SyncFileReceiver.h" +#include +#include +#include "ServerLogger.h" +#include "PathTraversal.h" +#include "HTTPObjectsBridge.h" +#include "UNSResponseBuilder.h" + +class SyncFileReceiver::Impl +{ +public: + bool EnableCORS = false; + bool HTMLResponse = false; + std::string TempRoot; + IPTablePtr BlockedIPs = nullptr; + WebFileInfoVec FileInfo; +}; + +SyncFileReceiver::SyncFileReceiver() : pimpl(std::make_unique()) +{ +} + +SyncFileReceiver::~SyncFileReceiver() = default; + +// 【修改】虚函数的默认实现:如果子类不重写,则默认返回原先的成功状态(201 Created) +uns::ResponsePtr SyncFileReceiver::ProcessFiles(const WebFileInfoVec& file_info, const std::string& tmp_root, uns::RequestPtr request) +{ + SCLOGF_ERROR("SyncFileReceiver::ProcessFiles default handler triggered. Files count: {}", file_info.size()); + + std::string resp_body = (pimpl->HTMLResponse ? EncodeUploadResultHTML() : EncodeUploadResult()); + + return (pimpl->EnableCORS ? uns::ResponseBuilder().Created().Body(resp_body).AutoCORS(request)() : uns::ResponseBuilder().Created().Body(resp_body)()); +} + +void SyncFileReceiver::SetResponseMode(bool html) +{ + pimpl->HTMLResponse = html; + SCLOG_DEBUG("SyncFileReceiver init mode: %s", (html ? "html" : "json")); +} + +void SyncFileReceiver::SetCORSEnable(bool enable) +{ + pimpl->EnableCORS = enable; + SCLOG_DEBUG("SyncFileReceiver CORS mode: %s", (enable ? "enabled" : "disabled")); +} + +void SyncFileReceiver::SetTempRoot(std::string temp_root) +{ + pimpl->TempRoot = temp_root; + SCLOG_TRACE("SFR-TempRoot: %s", pimpl->TempRoot.c_str()); + return; +} + +void SyncFileReceiver::UpdateBlockedIPs(IPTablePtr ip) +{ + pimpl->BlockedIPs = ip; + return; +} + +void SyncFileReceiver::AppenedBlockedIP(DateTime::Span block_time, std::string ip) +{ + DateTime expr_time = (DateTime::Now() += block_time); + IPList li{ ip }; + pimpl->BlockedIPs->Appened(expr_time, li); + pimpl->BlockedIPs->Update(); + SCLOG_INFO("IP: [%s] has been blocked untill {%s}", ip.c_str(), std::string(expr_time).c_str()); + return; +} + +uns::HTTPMethod SyncFileReceiver::GetMethod(uns::RequestPtr request) +{ + std::string method = request->GetImpl()->webcc_req->method(); + if (method == "GET") + return uns::HTTPMethod::H_GET; + else if (method == "PUT") + return uns::HTTPMethod::H_PUT; + else if (method == "POST") + return uns::HTTPMethod::H_POST; + else if (method == "HEAD") + return uns::HTTPMethod::H_HEAD; + else if (method == "TRACE") + return uns::HTTPMethod::H_TRACE; + else if (method == "PATCH") + return uns::HTTPMethod::H_PATCH; + else if (method == "DELETE") + return uns::HTTPMethod::H_DELETE; + else if (method == "OPTIONS") + return uns::HTTPMethod::H_OPTIONS; + else if (method == "CONNECT") + return uns::HTTPMethod::H_CONNECT; + else + return uns::HTTPMethod::H_UNKNOWN; +} + +bool SyncFileReceiver::WriteFile(const std::string& path, const std::string& bytes) +{ + std::ofstream stream{ path, std::ios::binary }; + if (stream.fail()) + { + SCLOG_WARNING("Failed to write file [%s]: can't open stream", path.c_str()); + return false; + } + stream.write(bytes.data(), bytes.size()); + if (stream.fail()) + SCLOG_WARNING("Failed to write file [%s]: can't write to stream", path.c_str()); + return !stream.fail(); +} + +uns::PathTraversalDefenceLevel SyncFileReceiver::PTDefence() +{ + return uns::PathTraversalDefenceLevel::DenyAll; +} + +bool SyncFileReceiver::IsPathSafe(const std::string & raw_path) +{ + return false; +} + +std::string SyncFileReceiver::EncodeUploadResult() +{ + Json::Value root; + Json::FastWriter writer; + root["AcceptedCount"] = pimpl->FileInfo.size(); + root["AcceptedFiles"] = Json::Value(Json::arrayValue); + for (auto& ele : pimpl->FileInfo) + { + Json::Value sub; + sub["FileName"] = ele.GetStorageFileName(); + sub["UploadTime"] = ele.GetUploadTime().GetTimeStamp(); + root["AcceptedFiles"].append(sub); + } + return writer.write(root); +} + +std::string SyncFileReceiver::EncodeUploadResultHTML() +{ + const char* html = R"( + + + + Upload Result + + +
+

Upload Result

+
+

AcceptedCount: %lld

+

AcceptedFiles:
%s

+
+ + +)"; + std::string tmp; + for (auto& ele : pimpl->FileInfo) + tmp += "[" + ele.GetStorageFileName() + "] - {" + ele.GetUploadTime().Format("%Y-%m-%d %H:%M:%S") + "}
"; + size_t html_size = strlen(html) + tmp.size() + 10; + char* result = new char[html_size]; + memset(result, 0, sizeof(result)); + sprintf(result, html, pimpl->FileInfo.size(), tmp.c_str()); + tmp = std::string(result); + delete[] result; + return tmp; +} + +uns::ResponsePtr SyncFileReceiver::Execute(uns::RequestPtr request) +{ + uns::HTTPMethod method = GetMethod(request); + std::string x_real_ip; + if(request->GetImpl()->webcc_req->HasHeader("X-Real-IP")) + x_real_ip = request->GetImpl()->webcc_req->GetHeader("X-Real-IP"); + std::string req_ip = (x_real_ip.empty() ? request->GetImpl()->webcc_req->address() : x_real_ip); + SCLOG_DEBUG("Request recived, ip: [%s], method: %s", req_ip.c_str(), request->GetImpl()->webcc_req->method().c_str()); + // path test + std::string path = request->GetImpl()->webcc_req->url().path(); + auto status = PathTraversal::AnalyzeUrlTraversal(path); + if(status != PathTraversal::UrlSafetyStatus::Safe) + SCLOGF_WARNING("PathTraversal Detected: {}, Level: {}", path, PathTraversal::ToString(status)); + switch(PTDefence()) + { + case uns::PathTraversalDefenceLevel::DenyAll: + if(status != PathTraversal::UrlSafetyStatus::Safe) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()()); + break; + case uns::PathTraversalDefenceLevel::AutoNormalize: + { + if(status == PathTraversal::UrlSafetyStatus::EvasiveTraversal) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()()); + auto decoded_path = PathTraversal::UrlDecode(path); + if(!IsPathSafe(decoded_path)) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()()); + auto url = request->GetImpl()->webcc_req->url(); + url.ForceSet_Path(PathTraversal::NormalizeUrlPath(decoded_path)); + request->GetImpl()->webcc_req->set_url(std::move(url)); + break; + } + case uns::PathTraversalDefenceLevel::AllowNormal: + { + if(status == PathTraversal::UrlSafetyStatus::EvasiveTraversal) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()()); + auto decoded_path = PathTraversal::UrlDecode(path); + if(!IsPathSafe(decoded_path)) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Forbidden().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Forbidden().EmptyBody()()); + auto url = request->GetImpl()->webcc_req->url(); + url.ForceSet_Path(decoded_path); + request->GetImpl()->webcc_req->set_url(std::move(url)); + break; + } + default: + break; //Check Bypassed by [AllowAll] + } + if ((method & uns::H_PUT) || (method & uns::H_POST)) + { + if (pimpl->BlockedIPs != nullptr) + { + std::string req_ip = request->GetImpl()->webcc_req->address(); + pimpl->BlockedIPs->Update(); + if (pimpl->BlockedIPs->IPExist(req_ip)) + return (pimpl->EnableCORS ? uns::ResponseBuilder().IPBlocked().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().IPBlocked().EmptyBody()()); + } + webcc::Status tmpStatus = uns::ConvertStatus(PreCheckRequest(request)); + if (tmpStatus != webcc::kOK) + return (pimpl->EnableCORS ? uns::ResponseBuilder().Code(tmpStatus).EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().Code(tmpStatus).EmptyBody()()); + else if (!request->IsForm()) + return (pimpl->EnableCORS ? uns::ResponseBuilder().RequestFormatError().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().RequestFormatError().EmptyBody()()); + else + { + for (auto& form : request->GetFormParts()) + { + if (form->GetFileName().empty()) + continue; + if (!PreCheckForm(form)) + { + SCLOGF_DEBUG("File denied: [{}], {} bytes", form->GetFileName(), form->GetDataSize()); + continue; + } + SCLOGF_DEBUG("File recived: [{}], {} bytes", form->GetFileName(), form->GetDataSize()); + WebFileInfo info(form->GetFileNameS(), form->GetDataSize()); + WriteFile(info.MakePath(pimpl->TempRoot), form->GetData()); + pimpl->FileInfo.push_back(info); + } + + // 【修改的关键步骤】 + // 1. 同步调用虚函数,获取具体的业务处理结果(及构筑好的自定义 HTTP Response) + uns::ResponsePtr response = ProcessFiles(pimpl->FileInfo, pimpl->TempRoot, request); + + // 2. 清理当前类中的文件缓存(防止污染下一次 HTTP 请求) + pimpl->FileInfo.clear(); + + // 3. 作为最后一步直接返回 + return response; + } + } + else + return (pimpl->EnableCORS ? uns::ResponseBuilder().IllegalUpload().EmptyBody().AutoCORS(request)() : uns::ResponseBuilder().IllegalUpload().EmptyBody()()); +} \ No newline at end of file diff --git a/UNSWebServerCore/SyncFileReceiver.h b/UNSWebServerCore/SyncFileReceiver.h new file mode 100644 index 0000000..7302aab --- /dev/null +++ b/UNSWebServerCore/SyncFileReceiver.h @@ -0,0 +1,43 @@ +#pragma once +#include "Export.h" +#include "Global.h" +#include "IPTable.h" +#include "WebFileInfo.h" +#include "HTTPObjects.h" + +class UNSWSC_DLL_EXPORT SyncFileReceiver +{ +protected: + class Impl; + std::unique_ptr pimpl; + +public: + SyncFileReceiver(); + virtual ~SyncFileReceiver(); + +public: + void SetResponseMode(bool html); + void SetCORSEnable(bool enable); + std::string EncodeUploadResult(); + std::string EncodeUploadResultHTML(); + void UpdateBlockedIPs(IPTablePtr ip); + void SetTempRoot(std::string temp_root); + 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); + +public: + virtual uns::Status PreCheckRequest(uns::RequestPtr request) = 0; + virtual bool PreCheckForm(uns::FormPartPtr form) = 0; + virtual uns::PathTraversalDefenceLevel PTDefence(); + virtual bool IsPathSafe(const std::string& raw_path); + + // 【修改】由原先的 Callback 改为可供子类重写的虚函数 + // 返回值改为 webcc::ResponsePtr,并且引入 request 参数以便子类调用 AutoCORS 或解析请求头 + virtual uns::ResponsePtr ProcessFiles(const WebFileInfoVec& file_info, const std::string& tmp_root, uns::RequestPtr request); + +public: + uns::ResponsePtr Execute(uns::RequestPtr request); +}; + +using SyncFileReceiverPtr = std::shared_ptr; \ No newline at end of file diff --git a/UNSWebServerCore/UNSResponseBuilder.cpp b/UNSWebServerCore/UNSResponseBuilder.cpp new file mode 100644 index 0000000..ba95e52 --- /dev/null +++ b/UNSWebServerCore/UNSResponseBuilder.cpp @@ -0,0 +1,778 @@ +#include "UNSResponseBuilder.h" +#include "Global.h" +#include +#include "CORSConfig.h" +#include "ServerLogger.h" +#include "HTTPObjectsBridge.h" +#include + +inline bool global_allow_framing = false; + +class uns::ResponseBuilder::Impl : public webcc::ResponseBuilder +{ +public: + bool empty_body = false; + bool framing_override = false; + bool allow_framing = global_allow_framing; + +public: + Impl() : webcc::ResponseBuilder() + { + } + + Impl(webcc::RequestPtr req) : webcc::ResponseBuilder(req) + { + } +}; + +bool uns::ResponseBuilder::FileExist(std::string file) +{ + std::fstream fs(file, std::ios::in); + bool succ = fs.is_open(); + if (succ) + fs.close(); + return succ; +} + +void uns::ResponseBuilder::SetGlobalAllowFraming(bool allow) +{ + global_allow_framing = allow; +} + +uns::ResponseBuilder::ResponseBuilder() : impl(std::make_unique()) +{ +} + +uns::ResponseBuilder::~ResponseBuilder() = default; + +uns::ResponseBuilder::ResponseBuilder(uns::RequestPtr req) +{ + auto raw_webcc_req = req->GetImpl()->webcc_req; + impl = std::make_unique(raw_webcc_req); +} + +uns::ResponsePtr uns::ResponseBuilder::operator()() +{ + //Auto appened error page when body is empty and not set as empty body + if((impl->GetCode() >= 400) && impl->IsBodyEmpty() && (!impl->empty_body)) + { + std::string body = uns::EncodeErrorPage(impl->GetCode()); + impl->Body(body); + } + if(impl->empty_body) + impl->Body(""); + SCLOGF_INFO("Response ready, Code: {}, Content-Length: {}", impl->GetCode(), impl->GetBodySize()); + auto& webcc_res_b = impl->Header("Date", uns::EncodeHTTPTime(nullptr)).Header("Server", G_SERVER_NAME); + bool framing = (impl->framing_override ? impl->allow_framing : global_allow_framing); + if(!framing) + webcc_res_b.Header("X-Frame-Options", "SAMEORIGIN").Header("Content-Security-Policy", "frame-ancestors 'self'"); + auto res_impl = std::make_unique(webcc_res_b()); + return uns::ResponsePtr(new uns::Response(std::move(res_impl))); +} + +uns::ResponseBuilder& uns::ResponseBuilder::Continue() +{ + impl->Code(webcc::kContinue); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::SwitchingProtocols() +{ + impl->Code(webcc::kSwitchingProtocols); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Processing() +{ + impl->Code(webcc::kProcessing); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::EarlyHints() +{ + impl->Code(webcc::kEarlyHints); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::OK() +{ + impl->OK(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Created() +{ + impl->Created(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Accepted() +{ + impl->Code(webcc::kAccepted); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::NonAuthoritativeInformation() +{ + impl->Code(webcc::kNonAuthoritativeInformation); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::NoContent() +{ + impl->Code(webcc::kNoContent); + EmptyBody(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::ResetContent() +{ + impl->Code(webcc::kResetContent); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::PartialContent() +{ + impl->Code(webcc::kPartialContent); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::MultiStatus() +{ + impl->Code(webcc::kMultiStatus); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::AlreadyReported() +{ + impl->Code(webcc::kAlreadyReported); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::IMUsed() +{ + impl->Code(webcc::kIMUsed); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::MultipleChoices() +{ + impl->Code(webcc::kMultipleChoices); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::MovedPermanently(const std::string& new_url) +{ + impl->Code(webcc::kMovedPermanently); + impl->Header("Location", new_url); + EmptyBody(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Found(const std::string& new_url) +{ + impl->Code(webcc::kFound); + impl->Header("Location", new_url); + EmptyBody(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::SeeOther(const std::string& new_url) +{ + impl->Code(webcc::kSeeOther); + impl->Header("Location", new_url); + EmptyBody(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::NotModified() +{ + impl->Code(webcc::kNotModified); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::UseProxy() +{ + impl->Code(webcc::kUseProxy); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::__Unused() +{ + impl->Code(webcc::k__Unused); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::TemporaryRedirect(const std::string& new_url) +{ + impl->Code(webcc::kTemporaryRedirect); + impl->Header("Location", new_url); + EmptyBody(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::PermanentRedirect(const std::string& new_url) +{ + impl->Code(webcc::kPermanentRedirect); + impl->Header("Location", new_url); + EmptyBody(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::BadRequest() +{ + impl->BadRequest(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Unauthorized() +{ + impl->Code(webcc::kUnauthorized); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::PaymentRequired() +{ + impl->Code(webcc::kPaymentRequired); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Forbidden() +{ + impl->Code(webcc::kForbidden); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::NotFound() +{ + impl->NotFound(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::MethodNotAllowed() +{ + impl->Code(webcc::kMethodNotAllowed); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::NotAcceptable() +{ + impl->Code(webcc::kNotAcceptable); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::ProxyAuthenticationRequired() +{ + impl->Code(webcc::kProxyAuthenticationRequired); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::RequestTimeout() +{ + impl->Code(webcc::kRequestTimeout); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Conflict() +{ + impl->Code(webcc::kConflict); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Gone() +{ + impl->Code(webcc::kGone); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::LengthRequired() +{ + impl->Code(webcc::kLengthRequired); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::PreconditionFailed() +{ + impl->Code(webcc::kPreconditionFailed); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::ContentTooLarge() +{ + impl->Code(webcc::kContentTooLarge); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::URITooLong() +{ + impl->Code(webcc::kURITooLong); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::UnsupportedMediaType() +{ + impl->Code(webcc::kUnsupportedMediaType); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::RangeNotSatisfiable() +{ + impl->Code(webcc::kRangeNotSatisfiable); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::ExpectationFailed() +{ + impl->Code(webcc::kExpectationFailed); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::IamATeapot() +{ + impl->Code(webcc::kIamATeapot); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::MisdirectedRequest() +{ + impl->Code(webcc::kMisdirectedRequest); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::UnprocessableContent() +{ + impl->Code(webcc::kUnprocessableContent); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Locked() +{ + impl->Code(webcc::kLocked); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::FailedDependency() +{ + impl->Code(webcc::kFailedDependency); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::TooEarly() +{ + impl->Code(webcc::kTooEarly); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::UpgradeRequired(const std::string& protocol) +{ + impl->Code(webcc::kMovedPermanently); + impl->Header("Upgrade", protocol); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::PreconditionRequired() +{ + impl->Code(webcc::kPreconditionRequired); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::TooManyRequests() +{ + impl->Code(webcc::kTooManyRequests); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::RequestHeaderFieldsTooLarge() +{ + impl->Code(webcc::kRequestHeaderFieldsTooLarge); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::UnavailableForLegalReasons() +{ + impl->Code(webcc::kUnavailableForLegalReasons); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::InternalServerError() +{ + impl->InternalServerError(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::NotImplemented() +{ + impl->NotImplemented(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::BadGateway() +{ + impl->Code(webcc::kBadGateway); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::ServiceUnavailable() +{ + impl->ServiceUnavailable(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::GatewayTimeout() +{ + impl->Code(webcc::kGatewayTimeout); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::HTTPVersionNotSupported() +{ + impl->Code(webcc::kHTTPVersionNotSupported); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::VariantAlsoNegotiates() +{ + impl->Code(webcc::kVariantAlsoNegotiates); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::InsufficientStorage() +{ + impl->Code(webcc::kInsufficientStorage); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::LoopDetected() +{ + impl->Code(webcc::kLoopDetected); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::NotExtended() +{ + impl->Code(webcc::kNotExtended); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::NetworkAuthenticationRequired() +{ + impl->Code(webcc::kNetworkAuthenticationRequired); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::RequestFormatError() +{ + impl->Code(webcc::k_uRequestFormatError); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::RequestInvalid() +{ + impl->Code(webcc::k_uRequestInvalid); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::URLOutOfRange() +{ + impl->Code(webcc::k_uURLOutOfRange); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::InvalidRequestHost() +{ + impl->Code(webcc::k_uInvalidRequestHost); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::IPBlocked() +{ + impl->Code(webcc::k_uIPBlocked); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::IllegalUpload() +{ + impl->Code(webcc::k_uIllegalUpload); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::FileFormatError() +{ + impl->Code(webcc::k_uFileFormatError); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::InvalidFile() +{ + impl->Code(webcc::k_uInvalidFile); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::FileTooLarge() +{ + impl->Code(webcc::k_uFileTooLarge); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::RPSLimited() +{ + impl->Code(webcc::k_uRPSLimited); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::SubProcessFalied() +{ + impl->Code(webcc::k_uSubProcessFalied); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::ServerHateYou() +{ + impl->Code(webcc::k_uServerHateYou); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::DoSFound() +{ + impl->Code(webcc::k_uDoSFound); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::DDoSFound() +{ + impl->Code(webcc::k_uDDoSFound); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::UnknownServerError() +{ + impl->Code(webcc::k_uUnknownServerError); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Utf8() +{ + impl->Utf8(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Json() +{ + impl->Json(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Date() +{ + impl->Header("Date", EncodeHTTPTime(nullptr)); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::GZip() +{ + impl->Gzip(); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::DenyFraming() +{ + impl->framing_override = true; + impl->allow_framing = false; + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::AllowFraming() +{ + impl->framing_override = true; + impl->allow_framing = true; + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Code(int code) +{ + impl->Code(static_cast(code)); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Charset(std::string_view charset) +{ + impl->Charset(charset); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::SetCookie(std::string_view cookie) +{ + impl->Header("Set-Cookie", cookie); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::MediaType(std::string_view media_type) +{ + impl->MediaType(media_type); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Header(std::string_view key, std::string_view value) +{ + impl->Header(key, value); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::EmptyBody() +{ + impl->empty_body = true; + impl->Body(""); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Body(int data) +{ + impl->Body(std::to_string(data)); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Body(std::string&& data) +{ + impl->Body(data); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Body(const Json::Value& data) +{ + Json::FastWriter writer; + impl->Json(); + impl->Body(writer.write(data)); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::Body(const std::string& data) +{ + impl->Body(data); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::ErrorPage() +{ + impl->Body(EncodeErrorPage(impl->GetCode())); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::ErrorPage(std::string_view error_page) +{ + impl->Body(error_page.data()); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::File(const std::filesystem::path& path, bool infer_media_type, std::size_t chunk_size) +{ + try + { + SCLOGF_INFO("Responsing With File: {}", path.generic_string()); + if (FileExist(path.generic_string())) + impl->File(path, infer_media_type, chunk_size); + else + { + impl->Code(webcc::kNotFound); + SCLOGF_WARNING("File {} Does not Exist", path.generic_string()); + } + } + catch (const webcc::Error& e) + { + SCLOGF_WARNING("webcc::Error During File Resopnse: {} ({})", e.code(), e.message()); + impl->Code(webcc::kNotFound); + } + catch (...) + { + impl->Code(webcc::kInternalServerError); + } + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::FileForDownload(const std::filesystem::path& path, bool infer_media_type, std::size_t chunk_size) +{ + try + { + SCLOGF_INFO("Responsing With File For Download: {}", path.generic_string()); + if (FileExist(path.generic_string())) + { + impl->File(path, infer_media_type, chunk_size); + impl->Header("Content-Disposition", "attachment;filename=" + path.filename().string()); + impl->Header("Content-Type", "application/octet-stream"); + } + else + { + impl->Code(webcc::kNotFound); + SCLOGF_WARNING("File {} Does not Exist", path.generic_string()); + } + } + catch (const webcc::Error& e) + { + SCLOGF_WARNING("webcc::Error During File-Downloading Resopnse: {} ({})", e.code(), e.message()); + impl->Code(webcc::kNotFound); + } + catch (...) + { + impl->Code(webcc::kInternalServerError); + } + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::AutoCORS(uns::RequestPtr req) +{ + auto Trim = [](const std::string& s) -> std::string + { + size_t start = 0; + while ((start < s.size()) && std::isspace(static_cast(s[start]))) + ++start; + if (start == s.size()) + return ""; + + size_t end = s.size() - 1; + while ((end > start) && std::isspace(static_cast(s[end]))) + --end; + return s.substr(start, end - start + 1); + }; + if(req->GetImpl()->webcc_req->HasHeader(uns::cors::reqh_o)) + { + std::string origin = Trim(req->GetImpl()->webcc_req->GetHeader(uns::cors::reqh_o)); + if(GlobalCORSConfig.UrlValidate(origin)) + { + 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); + return CORS(origin); + } + else + { + SCLOG_WARNING("Actual request: Host not allowed: %s", host.c_str()); + return Forbidden().Body(std::string("CORS ERROR")); + } + } + else + { + SCLOG_WARNING("Actual request: Origin not allowed: %s", origin.c_str()); + return Forbidden().Body(std::string("CORS ERROR")); + } + } + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::CORS(const std::string& origin) +{ + impl->Header("Vary", "Origin"); + impl->Header(uns::cors::resh_acao, origin); + impl->Header(uns::cors::resh_acac, uns::btos(GlobalCORSConfig.AllowCookie())); + return *this; +} + +uns::ResponseBuilder& uns::ResponseBuilder::CORS_Full(const std::string& origin, const std::set& headers) +{ + impl->Header("Vary", "Origin"); + impl->Header(uns::cors::resh_acao, origin); + impl->Header(uns::cors::resh_acam, GlobalCORSConfig.GetValidateMethods()); + impl->Header(uns::cors::resh_acah, GlobalCORSConfig.GetValidateHeaders(headers)); + impl->Header(uns::cors::resh_acac, uns::btos(GlobalCORSConfig.AllowCookie())); + impl->Header(uns::cors::resh_acma, std::to_string(GlobalCORSConfig.GetMaxAge())); + return *this; +} diff --git a/UNSWebServerCore/UNSResponseBuilder.h b/UNSWebServerCore/UNSResponseBuilder.h new file mode 100644 index 0000000..0871e8b --- /dev/null +++ b/UNSWebServerCore/UNSResponseBuilder.h @@ -0,0 +1,238 @@ +#pragma once +#include +#include +#include "Export.h" +#include +#include "HTTPObjects.h" + +namespace Json +{ + class Value; +} + +namespace uns +{ + class UNSWSC_DLL_EXPORT ResponseBuilder + { + private: + class Impl; + + std::unique_ptr impl; + + private: + bool FileExist(std::string file); + + public: + static void SetGlobalAllowFraming(bool allow); + + public: + ResponseBuilder(); + virtual ~ResponseBuilder(); + ResponseBuilder(uns::RequestPtr req); + ResponseBuilder(const ResponseBuilder&) = delete; + ResponseBuilder& operator=(const ResponseBuilder&) = delete; + + public: + uns::ResponsePtr operator()(); + + public: //Standard Return Code 1xx + //100 - 此临时响应表明客户端应继续请求,或者如果请求已完成,则忽略此响应。 + ResponseBuilder& Continue(); + //101 - 此代码是在响应客户端的 Upgrade 请求标头时发送的,用于指示服务器即将切换到的协议。 + ResponseBuilder& SwitchingProtocols(); + //102 - 此代码曾在 WebDAV 上下文中使用,表示服务器已收到请求,但在响应时无法提供状态。 + ResponseBuilder& Processing(); + //103 - 此状态码主要与 Link 标头一起使用,允许用户代理在服务器准备响应时开始预加载资源,或预连接到页面需要资源的源站。 + ResponseBuilder& EarlyHints(); + + public: //Standard Return Code 2xx + //200 - 请求成功。 + ResponseBuilder& OK(); + //201 - 请求成功,并因此创建了一个新资源。 + ResponseBuilder& Created(); + //202 - 请求已被接收但尚未处理。 + ResponseBuilder& Accepted(); + //203 - 此响应代码表示返回的元数据与原始服务器上可用的不完全相同,而是从本地或第三方副本收集的。这主要用于另一个资源的镜像或备份。 + ResponseBuilder& NonAuthoritativeInformation(); + //204 - 对于此请求,没有内容可发送,但响应头可能有用。 + ResponseBuilder& NoContent(); + //205 - 告知用户代理重置发送此请求的文档。 + ResponseBuilder& ResetContent(); + //206 - 当客户端请求了资源的一部分时,使用此响应代码进行响应。 + ResponseBuilder& PartialContent(); + //207 - 在可能需要多个状态码的情况下,传递关于多个资源的信息。 + ResponseBuilder& MultiStatus(); + //208 - 在 响应元素内部使用,以避免重复枚举同一集合的多个绑定的内部成员。 + ResponseBuilder& AlreadyReported(); + //226 - 服务器已完成了对资源的 GET 请求,并且响应是对当前实例应用了一个或多个实例操作后的结果表示。 + ResponseBuilder& IMUsed(); + + public: //Standard Return Code 3xx + //300 - 在代理驱动(agent-driven)的内容协商中,请求有多个可能的响应,用户代理或用户应选择其中之一。 + ResponseBuilder& MultipleChoices(); + //301 - 请求资源的 URL 已永久更改。新 URL 在响应中给出。 + ResponseBuilder& MovedPermanently(const std::string& new_url); + //302 - 此响应代码意味着请求资源的 URI 已暂时更改。未来可能还会对 URI 进行进一步更改,因此客户端在未来的请求中应使用相同的 URI。 + ResponseBuilder& Found(const std::string& new_url); + //303 - 服务器发送此响应以指示客户端使用 GET 请求在另一个 URI 获取请求的资源。 + ResponseBuilder& SeeOther(const std::string& new_url); + //304 - 用于缓存目的。它告知客户端响应未被修改,因此客户端可以继续使用相同的缓存响应版本。 + ResponseBuilder& NotModified(); + //305 - 在 HTTP 规范的前一版本中定义,表示请求的响应必须通过代理访问。由于涉及代理带内配置的安全问题,此状态码已被弃用。 + ResponseBuilder& UseProxy(); + //306 - 此响应代码不再使用,但被保留。它曾在 HTTP/1.1 规范的先前版本中使用。 + ResponseBuilder& __Unused(); + //307 - 服务器发送此响应以指示客户端使用与先前请求相同的方法在另一个 URI 获取请求的资源。其语义与 302 Found 响应代码相同,但用户代理不得更改使用的 HTTP 方法。 + ResponseBuilder& TemporaryRedirect(const std::string& new_url); + //308 - 表示资源现在永久位于另一个 URI,由 Location 响应头指定。其语义与 301 Moved Permanently HTTP 响应代码相同,但用户代理不得更改使用的 HTTP 方法。 + ResponseBuilder& PermanentRedirect(const std::string& new_url); + + public: //Recode Code 4xx + //400 - 由于被认为是客户端错误的原因(例如,格式错误的请求语法、无效的请求消息结构或欺骗性的请求路由),服务器无法或不会处理该请求。 + ResponseBuilder& BadRequest(); + //401 - 尽管 HTTP 标准指定为 "unauthorized",但从语义上讲,此响应的意思是 "unauthenticated"。即,客户端必须进行身份验证才能获得请求的响应。 + ResponseBuilder& Unauthorized(); + //402 - 此代码最初用于数字支付系统,但此状态码很少使用,且不存在标准约定。 + ResponseBuilder& PaymentRequired(); + //403 - 客户端没有访问内容的权利;也就是说,它是未授权的,因此服务器拒绝提供请求的资源。与 401 Unauthorized 不同,服务器知道客户端的身份。 + ResponseBuilder& Forbidden(); + //404 - 服务器找不到请求的资源。 + ResponseBuilder& NotFound(); + //405 - 服务器知道请求方法,但目标资源不支持该方法。 + ResponseBuilder& MethodNotAllowed(); + //406 - 当 Web 服务器执行服务器驱动的内容协商后,找不到任何符合用户代理给定条件的内容时,会发送此响应。 + ResponseBuilder& NotAcceptable(); + //407 - 类似于 401 Unauthorized,但需要通过代理进行身份验证。 + ResponseBuilder& ProxyAuthenticationRequired(); + //408 - 某些服务器会在空闲连接上发送此响应,即使客户端之前没有任何请求。这意味着服务器希望关闭此未使用的连接。 + ResponseBuilder& RequestTimeout(); + //409 - 当请求与服务器的当前状态冲突时,发送此响应。 + ResponseBuilder& Conflict(); + //410 - 当请求的内容已从服务器永久删除,且没有转发地址时,发送此响应。 + ResponseBuilder& Gone(); + //411 - 服务器拒绝了请求,因为未定义 Content-Length 标头字段,而服务器需要它。 + ResponseBuilder& LengthRequired(); + //412 - 在条件请求中,客户端在其标头中指明了服务器不满足的前提条件。 + ResponseBuilder& PreconditionFailed(); + //413 - 请求体大于服务器定义的限制。 + ResponseBuilder& ContentTooLarge(); + //414 - 客户端请求的 URI 长度超过了服务器愿意解释的长度。 + ResponseBuilder& URITooLong(); + //415 - 服务器不支持请求数据的媒体格式,因此服务器拒绝该请求。 + ResponseBuilder& UnsupportedMediaType(); + //416 - 无法满足请求中 Range 标头字段指定的范围。可能范围超出了目标资源数据的大小。 + ResponseBuilder& RangeNotSatisfiable(); + //417 - 此响应代码表示服务器无法满足 Expect 请求标头字段指示的期望。 + ResponseBuilder& ExpectationFailed(); + //418 - 服务器拒绝尝试用茶壶煮咖啡。 + ResponseBuilder& IamATeapot(); + //421 - 请求被发送到了一个无法产生响应的服务器。 + ResponseBuilder& MisdirectedRequest(); + //422 - 请求格式正确,但由于语义错误而无法被遵循。 + ResponseBuilder& UnprocessableContent(); + //423 - 正在访问的资源已被锁定。 + ResponseBuilder& Locked(); + //424 - 由于先前的请求失败,导致当前请求失败。 + ResponseBuilder& FailedDependency(); + //425 - 表示服务器不愿意冒险处理一个可能被重放的请求。 + ResponseBuilder& TooEarly(); + //426 - 服务器拒绝使用当前协议执行请求,但可能在客户端升级到其他协议后愿意执行。服务器在 426 响应中发送 Upgrade 标头以指示所需的协议。 + ResponseBuilder& UpgradeRequired(const std::string& protocol); + //428 - 原始服务器要求请求是有条件的。此响应旨在防止"丢失更新"问题,即客户端 GET 资源状态,修改后 PUT 回服务器,而同时第三方已修改了服务器上的状态,导致冲突。 + ResponseBuilder& PreconditionRequired(); + //429 - 用户在给定的时间内发送了太多请求(速率限制)。 + ResponseBuilder& TooManyRequests(); + //431 - 服务器因请求头字段太大而不愿意处理该请求。 + ResponseBuilder& RequestHeaderFieldsTooLarge(); + //451 - 用户代理请求了一个无法合法提供的资源,例如被政府审查的网页。 + ResponseBuilder& UnavailableForLegalReasons(); + + public: //Standard Return Code 5xx + //500 - 服务器遇到了不知道如何处理的情况。此错误是通用性的,表示服务器找不到更合适的 5XX 状态码来响应。 + ResponseBuilder& InternalServerError(); + //501 - 服务器不支持请求方法,无法处理。 + ResponseBuilder& NotImplemented(); + //502 - 此错误响应意味着服务器作为网关或代理时,收到了一个无效的响应。 + ResponseBuilder& BadGateway(); + //503 - 服务器尚未准备好处理请求。 + ResponseBuilder& ServiceUnavailable(); + //504 - 当服务器作为网关或代理,无法及时获得响应时,会给出此错误响应。 + ResponseBuilder& GatewayTimeout(); + //505 - 服务器不支持请求中使用的 HTTP 版本。 + ResponseBuilder& HTTPVersionNotSupported(); + //506 - 服务器存在内部配置错误:在内容协商过程中,被选中的变体被配置为自身参与内容协商,这导致在创建响应时出现循环引用。 + ResponseBuilder& VariantAlsoNegotiates(); + //507 - 由于服务器无法存储成功完成请求所需的表示,因此无法对资源执行该方法。 + ResponseBuilder& InsufficientStorage(); + //508 - 服务器在处理请求时检测到无限循环。 + ResponseBuilder& LoopDetected(); + //510 - 客户端请求声明了一个应使用 HTTP 扩展(RFC 2774)来处理请求,但该扩展不受支持。 + ResponseBuilder& NotExtended(); + //511 - 表示客户端需要进行身份验证才能获得网络访问权限。 + ResponseBuilder& NetworkAuthenticationRequired(); + + public: //Non-Standard Return Code 4xx + //489 - 请求载体的格式错误,如:无法解析的JSON等。 + ResponseBuilder& RequestFormatError(); + //490 - 请求无效。可能是由于未正确携带数据等必要信息。 + ResponseBuilder& RequestInvalid(); + //492 - 请求URL超范围。此响应表示请求的URL是错误的。 + ResponseBuilder& URLOutOfRange(); + //493 - 无效的请求主机。指示请求时使用了错误的域名/IP。 + ResponseBuilder& InvalidRequestHost(); + //494 - IP地址被封禁。 + ResponseBuilder& IPBlocked(); + //495 -非法上传请求。指示本次上传请求不符合服务器规定。 + ResponseBuilder& IllegalUpload(); + //496 - 文件格式错误。指示上传的文件格式不符合服务器规定。 + ResponseBuilder& FileFormatError(); + //497 - 无效文件。处理请求所需的文件已过期/无法访问。 + ResponseBuilder& InvalidFile(); + //498 - 上传的文件过大。非文件上传时应使用 413 Content Too Large。 + ResponseBuilder& FileTooLarge(); + //499 - 每秒请求数过多。仅在一些特殊API中使用,常规情况需使用 429 Too Many Requests。 + ResponseBuilder& RPSLimited(); + + public: //Non-Standard Code 5xx + //533 - 子过程失败。服务器在处理请求的某个步骤中遇到无法恢复的错误。 + ResponseBuilder& SubProcessFalied(); + //540 - 服务器检测到漏洞利用/可执行文件上传等网络攻击行为。 + ResponseBuilder& ServerHateYou(); + //550 - 检测到拒绝服务漏洞攻击。 + ResponseBuilder& DoSFound(); + //551 - 检测到分布式拒绝服务漏洞攻击。 + ResponseBuilder& DDoSFound(); + //560 - 未知的服务器错误。当服务器无法定位错误来源时返回。否则应使用 500 Internal Server Error。 + ResponseBuilder& UnknownServerError(); + + public: //Header Functions + ResponseBuilder& Utf8(); + ResponseBuilder& Json(); + ResponseBuilder& Date(); + ResponseBuilder& GZip(); + ResponseBuilder& DenyFraming(); + ResponseBuilder& AllowFraming(); + ResponseBuilder& Code(int code); + ResponseBuilder& Charset(std::string_view charset); + ResponseBuilder& SetCookie(std::string_view cookie); + ResponseBuilder& MediaType(std::string_view media_type); + ResponseBuilder& Header(std::string_view key, std::string_view value); + + public: //Body Functions + ResponseBuilder& EmptyBody(); + ResponseBuilder& Body(int data); + ResponseBuilder& Body(std::string&& data); + ResponseBuilder& Body(const Json::Value& data); + ResponseBuilder& Body(const std::string& data); + ResponseBuilder& ErrorPage(); + ResponseBuilder& ErrorPage(std::string_view error_page); + ResponseBuilder& File(const std::filesystem::path& path, bool infer_media_type = true, std::size_t chunk_size = 1024); + ResponseBuilder& FileForDownload(const std::filesystem::path& path, bool infer_media_type = true, std::size_t chunk_size = 1024); + + public: //CORS Functions + //Perform CORS Check and Return CORS Headers, Must be the last function called + ResponseBuilder& AutoCORS(uns::RequestPtr req); + ResponseBuilder& CORS(const std::string& origin); + ResponseBuilder& CORS_Full(const std::string& origin, const std::set& headers); + }; +}; \ No newline at end of file diff --git a/UNSWebServerCore/UNSWebServerCore.vcxproj b/UNSWebServerCore/UNSWebServerCore.vcxproj new file mode 100644 index 0000000..8fcef1c --- /dev/null +++ b/UNSWebServerCore/UNSWebServerCore.vcxproj @@ -0,0 +1,212 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {0c7bd295-ad5a-4643-a3e7-6bf6799c1467} + UNSWebServerCore + 10.0 + + + + DynamicLibrary + true + v145 + Unicode + + + DynamicLibrary + false + v145 + true + Unicode + + + DynamicLibrary + true + v145 + Unicode + + + DynamicLibrary + false + v145 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;UNSWEBSERVERCORE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp20 + Use + pch.h + + + Windows + true + false + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;UNSWEBSERVERCORE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp20 + Use + pch.h + + + Windows + true + false + + + true + + + + + Level3 + true + _DEBUG;UNSWEBSERVERCORE_EXPORTS;_WINDOWS;_USRDLL;_WIN32_WINNT=0x0A00;%(PreprocessorDefinitions) + true + stdcpp20 + NotUsing + pch.h + /utf-8 %(AdditionalOptions) + + + Windows + true + false + + + true + + + + + Level3 + true + true + true + NDEBUG;UNSWEBSERVERCORE_EXPORTS;_WINDOWS;_USRDLL;_WIN32_WINNT=0x0A00;%(PreprocessorDefinitions) + true + stdcpp20 + NotUsing + pch.h + /utf-8 %(AdditionalOptions) + + + Windows + true + false + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UNSWebServerCore/UNSWebServerCore.vcxproj.filters b/UNSWebServerCore/UNSWebServerCore.vcxproj.filters new file mode 100644 index 0000000..4cf1b92 --- /dev/null +++ b/UNSWebServerCore/UNSWebServerCore.vcxproj.filters @@ -0,0 +1,156 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/UNSWebServerCore/WebFileInfo.cpp b/UNSWebServerCore/WebFileInfo.cpp new file mode 100644 index 0000000..aa59eb9 --- /dev/null +++ b/UNSWebServerCore/WebFileInfo.cpp @@ -0,0 +1,92 @@ +#include +#ifndef _WIN32 +#include "UOHash.h" +#else +#include "../UOHash/UOHash.h" +#endif +#include "WebFileInfo.h" + +std::string WebFileInfo::GetRandomNumber() +{ + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(123456789, 987654321); + return std::to_string(dis(gen)); +} + +std::string WebFileInfo::GetExtension(std::string ofn) +{ + size_t pos = ofn.rfind('.'); + if (pos == -1) + return ""; + else + return ofn.substr(pos); +} + +std::string WebFileInfo::GenerateStorageName(std::string ofn) +{ + uns::HashResult result = uns::UOHash::HashString(uns::HashID::CRC32C, ofn); + std::string timestr = UploadTime.Format("UPLOAD-%Y%m%d%H%M%S-"); + uns::HashResult random = uns::UOHash::HashString(uns::HashID::CRC32C, GetRandomNumber()); + return timestr + std::string(result) + "-" + std::string(random) + ".unsctmp"; +} + +WebFileInfo::WebFileInfo(std::string ofn, size_t size) +{ + OriginalFileName = ofn; + FileSize = size; + UploadTime = DateTime::Now(); + StorageFileName = GenerateStorageName(ofn); + ExtensionName = GetExtension(ofn); +} + +WebFileInfo::WebFileInfo(std::string ofn, std::string sfn, std::string en, DateTime upt, size_t siz) +{ + OriginalFileName = ofn; + StorageFileName = sfn; + ExtensionName = en; + UploadTime = upt; + FileSize = siz; +} + +WebFileInfo::WebFileInfo(const WebFileInfo& obj) +{ + OriginalFileName = obj.OriginalFileName; + StorageFileName = obj.StorageFileName; + ExtensionName = obj.ExtensionName; + UploadTime = obj.UploadTime; + FileSize = obj.FileSize; +} + +std::string WebFileInfo::MakePath(std::string storage_dir) const +{ + if (storage_dir[storage_dir.size() - 1] == '/') + return storage_dir + StorageFileName; + else + return storage_dir + "/" + StorageFileName; +} + +std::string WebFileInfo::GetOriginalFileName() const +{ + return OriginalFileName; +} + +std::string WebFileInfo::GetStorageFileName() const +{ + return StorageFileName; +} + +std::string WebFileInfo::GetExtensionName() const +{ + return ExtensionName; +} + +DateTime WebFileInfo::GetUploadTime() const +{ + return UploadTime; +} + +size_t WebFileInfo::GetFileSize() const +{ + return FileSize; +} diff --git a/UNSWebServerCore/WebFileInfo.h b/UNSWebServerCore/WebFileInfo.h new file mode 100644 index 0000000..ba070a1 --- /dev/null +++ b/UNSWebServerCore/WebFileInfo.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include "Export.h" +#include "DateTime.h" + +class UNSWSC_DLL_EXPORT WebFileInfo +{ +private: + std::string OriginalFileName; + std::string StorageFileName; + std::string ExtensionName; + DateTime UploadTime; + size_t FileSize; + +private: + std::string GetRandomNumber(); + std::string GetExtension(std::string ofn); + std::string GenerateStorageName(std::string ofn); + +public: + WebFileInfo() = default; + WebFileInfo(std::string ofn, size_t size); + WebFileInfo(std::string ofn, std::string sfn, std::string en, DateTime upt, size_t siz); + WebFileInfo(const WebFileInfo& obj); + +public: + std::string MakePath(std::string storage_dir) const; + std::string GetOriginalFileName() const; + std::string GetStorageFileName() const; + std::string GetExtensionName() const; + DateTime GetUploadTime() const; + size_t GetFileSize() const; +}; + +using WebFileInfoVec = std::vector; \ No newline at end of file diff --git a/UNSWebServerCore/dllmain.cpp b/UNSWebServerCore/dllmain.cpp new file mode 100644 index 0000000..bdd9238 --- /dev/null +++ b/UNSWebServerCore/dllmain.cpp @@ -0,0 +1,16 @@ +// dllmain.cpp : 定义 DLL 应用程序的入口点。 +#include "pch.h" + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/UNSWebServerCore/framework.h b/UNSWebServerCore/framework.h new file mode 100644 index 0000000..80cbbc9 --- /dev/null +++ b/UNSWebServerCore/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/UNSWebServerCore/pch.cpp b/UNSWebServerCore/pch.cpp new file mode 100644 index 0000000..b6fb8f4 --- /dev/null +++ b/UNSWebServerCore/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: 与预编译标头对应的源文件 + +#include "pch.h" + +// 当使用预编译的头时,需要使用此源文件,编译才能成功。 diff --git a/UNSWebServerCore/pch.h b/UNSWebServerCore/pch.h new file mode 100644 index 0000000..9660927 --- /dev/null +++ b/UNSWebServerCore/pch.h @@ -0,0 +1,13 @@ +// pch.h: 这是预编译标头文件。 +// 下方列出的文件仅编译一次,提高了将来生成的生成性能。 +// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。 +// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。 +// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。 + +#ifndef PCH_H +#define PCH_H + +// 添加要在此处预编译的标头 +#include "framework.h" + +#endif //PCH_H diff --git a/UOHash/JsonUnicodeWriter.cpp b/UOHash/JsonUnicodeWriter.cpp new file mode 100644 index 0000000..8de69bd --- /dev/null +++ b/UOHash/JsonUnicodeWriter.cpp @@ -0,0 +1,17 @@ +#include "JsonUnicodeWriter.h" + +JsonUnicodeWriter::JsonUnicodeWriter() +{ + jswb["emitUTF8"] = true; + jswb["indentation"] = ""; + jswb["precisionType"] = "decimal"; + jswb["commentStyle"] = "None"; +} + +string JsonUnicodeWriter::write(Json::Value root) +{ + unique_ptrwriter(jswb.newStreamWriter()); + ostringstream oss; + writer->write(root, &oss); + return oss.str(); +} diff --git a/UOHash/JsonUnicodeWriter.h b/UOHash/JsonUnicodeWriter.h new file mode 100644 index 0000000..582d2dd --- /dev/null +++ b/UOHash/JsonUnicodeWriter.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include + +using std::string; +using std::unique_ptr; +using std::ostringstream; + +class JsonUnicodeWriter +{ +private: + Json::StreamWriterBuilder jswb; +public: + JsonUnicodeWriter(); +public: + string write(Json::Value root); +}; + diff --git a/UOHash/Public.cpp b/UOHash/Public.cpp new file mode 100644 index 0000000..f09dacb --- /dev/null +++ b/UOHash/Public.cpp @@ -0,0 +1,95 @@ +#include "Public.h" +#include "JsonUnicodeWriter.h" + +namespace uns +{ + HashResult::HashResult() + { + success = false; + } + + HashResult::HashResult(std::string res) + { + result = res; + success = true; + last_error = ""; + } + + HashResult::HashResult(const HashResult& obj) + { + result = obj.result; + success = obj.success; + last_error = obj.last_error; + } + + HashResult::HashResult(bool succ, std::string res, std::string err) + { + result = res; + success = succ; + if (!success) + last_error = err; + } + + HashResult HashResult::Faliure(std::string str) + { + return HashResult(false, "", str); + } + + HashResult HashResult::Success(std::string str) + { + return HashResult(true, str, ""); + } + + std::string HashResult::GetResult() + { + return result; + } + + std::string HashResult::GetLastError() + { + return last_error; + } + + std::string HashResult::EncodeJsonString(bool unicode, bool styled) + { + if (unicode) + { + JsonUnicodeWriter writer; + return writer.write(*this); + } + try + { + Json::Writer* writer = nullptr; + if (styled) + writer = new Json::StyledWriter(); + else + writer = new Json::FastWriter(); + std::string result = writer->write(*this); + delete writer; + return result; + } + catch (...) + { + return "{\"Success\":false,\"Result\":\"\",\"LastError\":\"STD C++ Error Catched\"}"; + } + } + + HashResult::operator bool() + { + return success; + } + + HashResult::operator std::string() + { + return (success ? result : last_error); + } + + HashResult::operator Json::Value() + { + Json::Value root; + root["Result"] = result; + root["Success"] = success; + root["LastError"] = last_error; + return root; + } +}; \ No newline at end of file diff --git a/UOHash/Public.h b/UOHash/Public.h new file mode 100644 index 0000000..5988c33 --- /dev/null +++ b/UOHash/Public.h @@ -0,0 +1,94 @@ +#pragma once +#include +#include +#include +#include +#pragma warning(disable : 4251) +#pragma warning(disable : 4996) + +#ifdef UOHASH_EXPORTS +#define UOHASH_DLL_API __declspec(dllexport) +#else +#define UOHASH_DLL_API __declspec(dllimport) +#ifdef _DEBUG +#pragma comment(lib, "../x64/Debug/UOHashd.lib") +#else +#pragma comment(lib, "../x64/Release/UOHash.lib") +#endif +#endif + +namespace uns +{ + enum class UOHASH_DLL_API HashID : std::int64_t + { + CRC32 = RHASH_CRC32, + MD4 = RHASH_MD4, + MD5 = RHASH_MD5, + SHA1 = RHASH_SHA1, + TIGER = RHASH_TIGER, + TTH = RHASH_TTH, + BTIH = RHASH_BTIH, + ED2K = RHASH_ED2K, + AICH = RHASH_AICH, + WHIRLPOOL = RHASH_WHIRLPOOL, + RIPEMD160 = RHASH_RIPEMD160, + GOST94 = RHASH_GOST94, + GOST94_CRYPTOPRO = RHASH_GOST94_CRYPTOPRO, + HAS160 = RHASH_HAS160, + GOST12_256 = RHASH_GOST12_256, + GOST12_512 = RHASH_GOST12_512, + SHA224 = RHASH_SHA224, + SHA256 = RHASH_SHA256, + SHA384 = RHASH_SHA384, + SHA512 = RHASH_SHA512, + EDONR256 = RHASH_EDONR256, + EDONR512 = RHASH_EDONR512, + SHA3_224 = RHASH_SHA3_224, + SHA3_256 = RHASH_SHA3_256, + SHA3_384 = RHASH_SHA3_384, + SHA3_512 = RHASH_SHA3_512, + CRC32C = RHASH_CRC32C, + SNEFRU128 = RHASH_SNEFRU128, + SNEFRU256 = RHASH_SNEFRU256, + BLAKE2S = RHASH_BLAKE2S, + BLAKE2B = RHASH_BLAKE2B, //0x40000000 + __RHASH_MAX = 0b01111111111111111111111111111111, + SHAKE128 = 0x80000000, + SHAKE256 = 0x100000000, + BASE16 = 0x200000000, + BASE32 = 0x400000000, + BASE32_HEX = 0x800000000, + BASE36 = 0x1000000000, + BASE58 = 0x2000000000, + BASE62 = 0x4000000000, + BASE64 = 0x8000000000, + BASE64_URL = 0x10000000000, + BASE85 = 0x20000000000, + BASE91 = 0x40000000000, + URL = 0x80000000000 + }; + + class UOHASH_DLL_API HashResult + { + private: + bool success; + std::string result; + std::string last_error; + public: + HashResult(); + HashResult(std::string res); + HashResult(const HashResult& obj); + HashResult(bool succ, std::string res, std::string err); + public: + static HashResult Faliure(std::string str); + static HashResult Success(std::string str); + public: + std::string GetResult(); + std::string GetLastError(); + std::string EncodeJsonString(bool unicode = false, bool styled = false); + public: + operator bool(); + operator std::string(); + operator Json::Value(); + }; +}; \ No newline at end of file diff --git a/UOHash/UOHash.cpp b/UOHash/UOHash.cpp new file mode 100644 index 0000000..03483fa --- /dev/null +++ b/UOHash/UOHash.cpp @@ -0,0 +1,394 @@ +#include +#include +#include "UOHash.h" +#include +#include "URLCodec.h" +#include "basecodec.hpp" +#include "base36_codec.hpp" +#include +#include +#include +#include +#include + +namespace uns +{ + bool UOHash::FileExist(std::string file) + { + std::fstream fs(file, std::ios::in); + if (fs.is_open()) + { + fs.close(); + return true; + } + else + return false; + } + + int UOHash::CalculateHashSize(HashID HashID) + { + switch (HashID) + { + case HashID::CRC32: return 8; + case HashID::MD4: return 32; + case HashID::MD5: return 32; + case HashID::SHA1: return 40; + case HashID::TIGER: return 48; + case HashID::TTH: return 39; + case HashID::BTIH: return 40; + case HashID::ED2K: return 32; + case HashID::AICH: return 32; + case HashID::WHIRLPOOL: return 128; + case HashID::RIPEMD160: return 40; + case HashID::GOST94: return 64; + case HashID::GOST94_CRYPTOPRO: return 64; + case HashID::HAS160: return 40; + case HashID::GOST12_256: return 64; + case HashID::GOST12_512: return 128; + case HashID::SHA224: return 56; + case HashID::SHA256: return 64; + case HashID::SHA384: return 96; + case HashID::SHA512: return 128; + case HashID::EDONR256: return 64; + case HashID::EDONR512: return 128; + case HashID::SHA3_224: return 56; + case HashID::SHA3_256: return 64; + case HashID::SHA3_384: return 96; + case HashID::SHA3_512: return 128; + case HashID::CRC32C: return 8; + case HashID::SNEFRU128: return 32; + case HashID::SNEFRU256: return 64; + case HashID::BLAKE2S: return 64; + case HashID::BLAKE2B: return 128; + default: return 0; + } + return -1; + } + + std::string UOHash::RemoveWhiteChar(std::string str) + { + std::string ret; + for (auto& ele : str) + if ((ele != ' ') && (ele != '\n') && (ele != '\t')) + ret.push_back(ele); + return ret; + } + + int UOHash::CalculateHashExSize(HashID HashID, int output_bits) + { + if (HashID == HashID::SHAKE128) + return 64; + else if (HashID == HashID::SHAKE256) + return 128; + else + return 0; + } + + rhash_print_sum_flags UOHash::PickOutputType(HashID HashID) + { + if ((HashID == HashID::TTH) || (HashID == HashID::AICH)) + return RHPR_BASE32; + else + return RHPR_HEX; + } + + HashResult UOHash::DecodeString(HashID HashID, std::string Source) + { + if (HashID < HashID::BASE16) + return HashResult::Faliure(INVALID_HASH_FUNCTION); + else + { + switch (HashID) + { + case HashID::BASE16: + { + std::string output; + if (basecodec::decodeBase16(Source, output)) + return HashResult::Success(output); + else + return HashResult::Faliure(HASH_CALCULATION_ERROR); + } + case HashID::BASE32: + { + std::string ret = ""; + CryptoPP::StringSource source(Source, true, new CryptoPP::Base32Decoder(new CryptoPP::StringSink(ret))); + if (ret == "") + return HashResult::Faliure(HASH_CALCULATION_ERROR); + else + return HashResult::Success(ret); + } + case HashID::BASE32_HEX: + { + std::string ret = ""; + CryptoPP::StringSource source(Source, true, new CryptoPP::Base32HexDecoder(new CryptoPP::StringSink(ret))); + if (ret == "") + return HashResult::Faliure(HASH_CALCULATION_ERROR); + else + return HashResult::Success(ret); + } + case HashID::BASE36: + { + std::string out; + if (basecodec::decodeBase36ToDecimalString(Source, out)) + return HashResult::Success(out); + else + return HashResult::Faliure(HASH_CALCULATION_ERROR); + } + case HashID::BASE62: + { + std::string out; + if (basecodec::decodeBase62(Source, out)) + return HashResult::Success(out); + else + return HashResult::Faliure(HASH_CALCULATION_ERROR); + } + case HashID::BASE58: + { + std::string out; + if (basecodec::decodeBase58(Source, out)) + return HashResult::Success(out); + else + return HashResult::Faliure(HASH_CALCULATION_ERROR); + } + case HashID::BASE64: + { + std::string ret = ""; + CryptoPP::StringSource source(Source, true, new CryptoPP::Base64Decoder(new CryptoPP::StringSink(ret))); + if (ret == "") + return HashResult::Faliure(HASH_CALCULATION_ERROR); + else + return HashResult::Success(ret); + } + case HashID::BASE64_URL: + { + std::string ret = ""; + CryptoPP::StringSource source(Source, true, new CryptoPP::Base64URLDecoder(new CryptoPP::StringSink(ret))); + if (ret == "") + return HashResult::Faliure(HASH_CALCULATION_ERROR); + else + return HashResult::Success(ret); + } + case HashID::BASE85: + { + std::string output; + if (basecodec::decodeBase85(Source, output)) + return HashResult::Success(output); + else + return HashResult::Faliure(HASH_CALCULATION_ERROR); + } + case HashID::BASE91: + { + std::string output; + if (basecodec::decodeBase91(Source, output)) + return HashResult::Success(output); + else + return HashResult::Faliure(HASH_CALCULATION_ERROR); + } + case HashID::URL: + { + std::string out; + if (urlcodec::url_decode(Source, out)) + return HashResult::Success(out); + else + return HashResult::Faliure(HASH_CALCULATION_ERROR); + } + default: + return HashResult::Faliure(INVALID_HASH_FUNCTION); + } + return HashResult::Faliure(INVALID_HASH_FUNCTION); + } + } + + HashResult UOHash::HashString(HashID HashID, std::string Source, int ex_output_bits) + { + if (HashID >= HashID::__RHASH_MAX) + { + switch (HashID) + { + case HashID::SHAKE128: + { + if (ex_output_bits <= 0) + return HashResult::Faliure(INVALID_HASHEX_ARGUMENT); + CryptoPP::SHAKE128 shake128(ex_output_bits); + int hash_size = CalculateHashExSize(HashID, ex_output_bits); + unsigned char* recv = new unsigned char[ex_output_bits + 10]; + char* hashstr = new char[hash_size + 10]; + memset(recv, 0, (ex_output_bits + 10)); + memset(hashstr, 0, (hash_size + 10)); + shake128.CalculateDigest(recv, (const unsigned char*)Source.data(), Source.size()); + rhash_print_bytes(hashstr, recv, (hash_size / 2), (RHPR_HEX | RHPR_UPPERCASE)); + std::string str(hashstr); + delete[] recv; + recv = nullptr; + delete[] hashstr; + hashstr = nullptr; + return HashResult::Success(RemoveWhiteChar(str)); + } + case HashID::SHAKE256: + { + if (ex_output_bits <= 0) + return HashResult::Faliure(INVALID_HASHEX_ARGUMENT); + CryptoPP::SHAKE256 shake256(ex_output_bits); + int hash_size = CalculateHashExSize(HashID, ex_output_bits); + unsigned char* recv = new unsigned char[ex_output_bits + 10]; + char* hashstr = new char[hash_size + 10]; + memset(recv, 0, (ex_output_bits + 10)); + memset(hashstr, 0, (hash_size + 10)); + shake256.CalculateDigest(recv, (const unsigned char*)Source.data(), Source.size()); + rhash_print_bytes(hashstr, recv, (hash_size / 2), (RHPR_HEX | RHPR_UPPERCASE)); + std::string str(hashstr); + delete[] recv; + recv = nullptr; + delete[] hashstr; + hashstr = nullptr; + return HashResult::Success(RemoveWhiteChar(str)); + } + case HashID::BASE16: + return HashResult::Success(basecodec::encodeBase16(Source)); + case HashID::BASE32: + { + try + { + std::string ret = ""; + CryptoPP::Base32Encoder b32enc; + b32enc.Detach(new CryptoPP::StringSink(ret)); + b32enc.Put(reinterpret_cast(&Source[0]), Source.size()); + if (ret == "") + return HashResult::Faliure(HASH_CALCULATION_ERROR); + else + return HashResult::Success(RemoveWhiteChar(ret)); + } + catch (...) + { + return HashResult::Faliure(HASH_LIBRARY_ERROR); + } + } + case HashID::BASE32_HEX: + { + try + { + std::string ret = ""; + CryptoPP::Base32HexEncoder b32enc_hex; + b32enc_hex.Detach(new CryptoPP::StringSink(ret)); + b32enc_hex.Put(reinterpret_cast(&Source[0]), Source.size()); + if (ret == "") + return HashResult::Faliure(HASH_CALCULATION_ERROR); + else + return HashResult::Success(RemoveWhiteChar(ret)); + } + catch (...) + { + return HashResult::Faliure(HASH_LIBRARY_ERROR); + } + } + case HashID::BASE36: + { + std::string out; + if (basecodec::encodeBase36FromDecimalString(Source, out)) + return HashResult::Success(out); + else + return HashResult::Faliure(HASH_CALCULATION_ERROR); + } + case HashID::BASE58: + return HashResult::Success(basecodec::encodeBase58(Source)); + case HashID::BASE62: + return HashResult::Success(basecodec::encodeBase62(Source)); + case HashID::BASE64: + { + try + { + std::string ret = ""; + CryptoPP::Base64Encoder b64enc; + b64enc.Detach(new CryptoPP::StringSink(ret)); + b64enc.Put(reinterpret_cast(&Source[0]), Source.size()); + if (ret == "") + return HashResult::Faliure(HASH_CALCULATION_ERROR); + else + return HashResult::Success(RemoveWhiteChar(ret)); + } + catch (...) + { + return HashResult::Faliure(HASH_LIBRARY_ERROR); + } + } + case HashID::BASE64_URL: + { + try + { + std::string ret = ""; + CryptoPP::Base64URLEncoder b64enc_url; + b64enc_url.Detach(new CryptoPP::StringSink(ret)); + b64enc_url.Put(reinterpret_cast(&Source[0]), Source.size()); + if (ret == "") + return HashResult::Faliure(HASH_CALCULATION_ERROR); + else + return HashResult::Success(RemoveWhiteChar(ret)); + } + catch (...) + { + return HashResult::Faliure(HASH_LIBRARY_ERROR); + } + } + case HashID::BASE85: + return HashResult::Success(basecodec::encodeBase85(Source)); + case HashID::BASE91: + { + std::string out; + if (basecodec::encodeBase91(Source, out)) + return HashResult::Success(out); + else + return HashResult::Faliure(HASH_CALCULATION_ERROR); + } + case HashID::URL: + return HashResult::Success(urlcodec::url_encode(Source)); + default: + return HashResult::Faliure(INVALID_HASH_FUNCTION); + } + return HashResult::Faliure(INVALID_HASH_FUNCTION); + } + else + { + int HashSize = CalculateHashSize(HashID); + if (HashSize < 8) + return HashResult::Faliure(INVALID_HASH_FUNCTION); + char* src = new char[Source.length() + 1]; + unsigned char* dst = new unsigned char[(HashSize * 4) + 1]; + char* str = new char[HashSize + 1]; + strcpy(src, Source.c_str()); + memset(dst, 0, sizeof(dst)); + memset(str, 0, sizeof(str)); + rhash_library_init(); + int res = rhash_msg((int)HashID, src, strlen(src), dst); + if (res < 0) + return HashResult::Faliure(HASH_CALCULATION_ERROR); + rhash_print_bytes(str, dst, rhash_get_digest_size((int)HashID), (PickOutputType(HashID) | RHPR_UPPERCASE)); + std::string ret = std::string(str); + delete[] src; + delete[] dst; + delete[] str; + return HashResult((ret.length() >= 8), RemoveWhiteChar(ret), HASH_LIBRARY_ERROR); + } + } + + HashResult UOHash::HashFile(HashID HashID, std::string File, int ex_output_bits) + { + if (HashID >= HashID::__RHASH_MAX) + return HashResult::Faliure(INVALID_HASH_FUNCTION); + if (!FileExist(File)) + return HashResult::Faliure(FILE_DOES_NOT_EXIST); + int HashSize = CalculateHashSize(HashID); + if (HashSize < 8) + return HashResult::Faliure(INVALID_HASH_FUNCTION); + unsigned char* dst = new unsigned char[(HashSize * 4) + 1]; + char* str = new char[HashSize + 1]; + rhash_library_init(); + int res = rhash_file((int)HashID, File.c_str(), dst); + if (res < 0) + return HashResult::Faliure(HASH_CALCULATION_ERROR); + rhash_print_bytes(str, dst, rhash_get_digest_size((int)HashID), (PickOutputType(HashID) | RHPR_UPPERCASE)); + std::string ret = std::string(str); + delete[] dst; + delete[] str; + return HashResult((ret.length() >= 8), RemoveWhiteChar(ret), HASH_LIBRARY_ERROR); + } +}; diff --git a/UOHash/UOHash.h b/UOHash/UOHash.h new file mode 100644 index 0000000..c934037 --- /dev/null +++ b/UOHash/UOHash.h @@ -0,0 +1,29 @@ +#pragma once +#include "Public.h" + +namespace uns +{ + class UOHASH_DLL_API UOHash + { + public: + UOHash() = delete; + UOHash(const UOHash& obj) = delete; + private: + static constexpr auto INVALID_HASHEX_ARGUMENT = "Invalid HashEx Argument"; + static constexpr auto HASH_CALCULATION_ERROR = "Hash Calculation Error"; + static constexpr auto INVALID_HASH_FUNCTION = "Invalid Hash Function"; + static constexpr auto FILE_DOES_NOT_EXIST = "File Doesn't Exist"; + static constexpr auto HASH_LIBRARY_ERROR = "Hash Library Error"; + static constexpr auto HASH_FUNCTION_CODING = "Hash Function Developing"; + private: + static bool FileExist(std::string file); + static int CalculateHashSize(HashID HashID); + static std::string RemoveWhiteChar(std::string str); + static int CalculateHashExSize(HashID HashID, int output_bits = 0); + static rhash_print_sum_flags PickOutputType(HashID HashID); + public: + static HashResult DecodeString(HashID HashID, std::string Source); + static HashResult HashString(HashID HashID, std::string Source, int ex_output_bits = 128); + static HashResult HashFile(HashID HashID, std::string File, int ex_output_bits = 128); + }; +}; \ No newline at end of file diff --git a/UOHash/UOHash.vcxproj b/UOHash/UOHash.vcxproj new file mode 100644 index 0000000..83b7218 --- /dev/null +++ b/UOHash/UOHash.vcxproj @@ -0,0 +1,182 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {696fff0a-4392-4ea6-8e41-1cb3867c4366} + UOHash + 10.0 + + + + DynamicLibrary + true + v145 + Unicode + + + DynamicLibrary + false + v145 + true + Unicode + + + DynamicLibrary + true + v145 + Unicode + + + DynamicLibrary + false + v145 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(ProjectName)d + + + + Level3 + true + WIN32;_DEBUG;UOHASH_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp20 + Use + pch.h + + + Windows + true + false + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;UOHASH_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp20 + Use + pch.h + + + Windows + true + false + + + true + + + + + Level3 + true + _DEBUG;UOHASH_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp20 + NotUsing + pch.h + + + Windows + true + false + + + true + + + + + Level3 + true + true + true + NDEBUG;UOHASH_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp20 + NotUsing + pch.h + + + Windows + true + false + + + true + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + \ No newline at end of file diff --git a/UOHash/UOHash.vcxproj.filters b/UOHash/UOHash.vcxproj.filters new file mode 100644 index 0000000..ae50ecb --- /dev/null +++ b/UOHash/UOHash.vcxproj.filters @@ -0,0 +1,63 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/UOHash/URLCodec.cpp b/UOHash/URLCodec.cpp new file mode 100644 index 0000000..10851aa --- /dev/null +++ b/UOHash/URLCodec.cpp @@ -0,0 +1,47 @@ +#include "URLCodec.h" +#include +#include +#include + +// 编码函数实现 +// 参考 Python 的 urllib.parse.quote 实现 +std::string urlcodec::url_encode(const std::string& input) +{ + std::ostringstream oss; + for (const auto& ch : input) + { + // 保留字母、数字和部分符号 + if (std::isalnum(static_cast(ch)) || ch == '-' || ch == '_' || ch == '.' || ch == '~') + oss << ch; + else // 其他字符进行百分号编码 + oss << '%' << std::uppercase << std::setw(2) << std::setfill('0') << std::hex << static_cast(static_cast(ch)); + } + return oss.str(); +} + +// 解码函数实现 +// 参考 Python 的 urllib.parse.unquote 实现 +bool urlcodec::url_decode(const std::string& input, std::string& output) +{ + std::ostringstream oss; + size_t length = input.length(); + + for (size_t i = 0; i < length; ++i) + { + if (input[i] == '%') + { + if (i + 2 >= length) + return false; // 错误:不完整的百分号编码 + std::string hex_str = input.substr(i + 1, 2); + if (!std::isxdigit(hex_str[0]) || !std::isxdigit(hex_str[1])) + return false; // 错误:无效的十六进制字符 + char decoded_char = static_cast(std::stoi(hex_str, nullptr, 16)); + oss << decoded_char; + i += 2; // 跳过已处理的两个字符 + } + else + oss << input[i]; + } + output = oss.str(); + return true; +} diff --git a/UOHash/URLCodec.h b/UOHash/URLCodec.h new file mode 100644 index 0000000..daac32a --- /dev/null +++ b/UOHash/URLCodec.h @@ -0,0 +1,18 @@ +#pragma once +#include + +namespace urlcodec +{ + // ±àÂ뺯Êý£º½«ÊäÈë×ַû´®½øÐÐ URL ±àÂë + // ÀàËÆÓÚ Python µÄ urllib.parse.quote + // ÊäÈ룺ԭʼ×ַû´® + // Êä³ö£º±àÂëºóµÄ×ַû´® + std::string url_encode(const std::string& input); + + // ½âÂ뺯Êý£º½« URL ±àÂëµÄ×ַû´®½âÂëΪԭʼ×ַû´® + // ÀàËÆÓÚ Python µÄ urllib.parse.unquote + // ÊäÈ룺±àÂëºóµÄ×ַû´® + // Êä³ö£ºԭʼ×ַû´® + // ·µ»Øֵ£ºtrue ±íʾ½âÂë³ɹ¦£¬false ±íʾ½âÂë¹ý³ÌÖгöÏִíÎó + bool url_decode(const std::string& input, std::string& output); +} diff --git a/UOHash/base36_codec.hpp b/UOHash/base36_codec.hpp new file mode 100644 index 0000000..1918664 --- /dev/null +++ b/UOHash/base36_codec.hpp @@ -0,0 +1,139 @@ +#pragma once +// base36_codec.hpp +#ifndef BASE36_CODEC_HPP +#define BASE36_CODEC_HPP + +#include +#include +#include +#include + +//By ChatGPT: 1:1 converted from base36 library source code +namespace basecodec +{ + // Base36 alphabet + inline constexpr const char* base36_alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"; + + // Python: line 17 + // def dumps(number): + inline bool encodeBase36(int64_t number, std::string& result) noexcept + { + result.clear(); + + // Python: line 23 + if (number < 0) + { + std::string positive; + if (!encodeBase36(-number, positive)) + return false; + result = '-' + positive; + return true; + } + + // Python: line 26 + if (number == 0) + { + result = "0"; + return true; + } + + std::string value; + while (number != 0) + { + int remainder = number % 36; + number /= 36; + value.insert(value.begin(), base36_alphabet[remainder]); + } + + result = std::move(value); + return true; + } + + inline bool encodeBase36FromDecimalString(const std::string& decimalStr, std::string& base36Out) noexcept + { + uint64_t number = 0; + auto [ptr, ec] = std::from_chars(decimalStr.data(), decimalStr.data() + decimalStr.size(), number, 10); + if (ec != std::errc()) + return false; + + static const char alphabet[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + + std::string result; + do + { + result.insert(result.begin(), alphabet[number % 36]); + number /= 36; + } + while (number != 0); + + base36Out = result; + return true; + } + + // Python: line 35 + // def loads(value): + inline bool decodeBase36(const std::string& input, int64_t& number) noexcept + { + // Validate input: allow optional leading '-', rest must be base36 chars + size_t start = 0; + bool negative = false; + if (!input.empty() && input[0] == '-') + { + negative = true; + start = 1; + } + + if (start == input.size()) + return false; // "-" alone is invalid + + number = 0; + for (size_t i = start; i < input.size(); ++i) + { + char c = std::tolower(input[i]); + int digit; + if (c >= '0' && c <= '9') + digit = c - '0'; + else if (c >= 'a' && c <= 'z') + digit = c - 'a' + 10; + else + return false; // invalid character +#pragma push_macro("max") +#undef max + if (number > (std::numeric_limits::max() - digit) / 36) + return false; // overflow +#pragma pop_macro("max") + number = number * 36 + digit; + } + + if (negative) + number = -number; + return true; + } + + inline bool decodeBase36ToDecimalString(const std::string& base36Str, std::string& decimalOut) noexcept + { + uint64_t number = 0; + for (char c : base36Str) + { + int digit = 0; + if (c >= '0' && c <= '9') + digit = c - '0'; + else if (c >= 'a' && c <= 'z') + digit = c - 'a' + 10; + else if (c >= 'A' && c <= 'Z') + digit = c - 'A' + 10; // case-insensitive + else + return false; // invalid character + if (digit >= 36) + return false; + number = number * 36 + digit; + } + + // Convert number to string using std::to_string + decimalOut = std::to_string(number); + return true; + } + +} // namespace basecodec + +#endif // BASE36_CODEC_HPP diff --git a/UOHash/basecodec.hpp b/UOHash/basecodec.hpp new file mode 100644 index 0000000..ed394b3 --- /dev/null +++ b/UOHash/basecodec.hpp @@ -0,0 +1,508 @@ +#pragma once +#ifndef BASECODEC_HPP +#define BASECODEC_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//By ChatGPT: 1:1 converted from cpython library source code +//base58 from base58 library +namespace basecodec +{ + + // ------------------------- + // Base16 (hex) encoding/decoding + // Converted from Python base16 implementation: + // def b16encode(s): return binascii.hexlify(s).upper() + // def b16decode(s, casefold=False): ... binascii.unhexlify(s) + // Python source around lines 290-320 in base64.py + // ------------------------- + + // Encode to hex, always succeeds + inline std::string b16encode(const std::vector& data) + { + std::ostringstream oss; + oss << std::uppercase << std::hex; + for (auto byte : data) + oss << std::setw(2) << std::setfill('0') << static_cast(byte); + return oss.str(); + } + + // Decode from hex, returns success status; outputs bytes in 'out' + inline bool b16decode(const std::string& s, std::vector& out, bool casefold = false) + { + std::string str = s; + if (casefold) + { + for (auto& c : str) + c = std::toupper(static_cast(c)); + } + if (str.size() % 2 != 0) + return false; // invalid length + out.clear(); + out.reserve(str.size() / 2); + for (size_t i = 0; i < str.size(); i += 2) + { + auto val = [&] (char c) -> int + { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; + }; + int high = val(str[i]); + int low = val(str[i + 1]); + if (high < 0 || low < 0) + { + out.clear(); + return false; // non-hex digit + } + out.push_back(static_cast((high << 4) | low)); + } + return true; + } + + // ------------------------- + // Base85 encoding/decoding (Z85-compatible) + // Converted from Python base85 implementation: + // def b85encode(b, pad=False): ... _85encode(...) + // def b85decode(b): ... + // Python source around lines 550-650 in base64.py + // ------------------------- + + // Base85 alphabet as per Python _b85alphabet + inline const std::string& _b85alphabet() + { + static const std::string alphabet = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; + return alphabet; + } + + // Precomputed table of 85x85 two-character combos + inline const std::vector& _b85chars2() + { + static std::vector table; + if (table.empty()) + { + const auto& alph = _b85alphabet(); + table.reserve(85 * 85); + for (char a : alph) + for (char b : alph) + table.emplace_back(std::string{a, b}); + } + return table; + } + + // Encode bytes to Z85 string, always succeeds + inline std::string b85encode(const std::vector& data, bool pad = false) + { + const auto& alph = _b85alphabet(); + const auto& table2 = _b85chars2(); + size_t len = data.size(); + size_t padding = (4 - (len % 4)) % 4; + std::vector bytes = data; + if (padding) + bytes.insert(bytes.end(), padding, 0); + + std::string result; + result.reserve((bytes.size() / 4) * 5); + for (size_t i = 0; i < bytes.size(); i += 4) + { + uint32_t acc = + (uint32_t(bytes[i]) << 24) | + (uint32_t(bytes[i + 1]) << 16) | + (uint32_t(bytes[i + 2]) << 8) | + uint32_t(bytes[i + 3]); + uint32_t idx1 = acc / 614125; // 85^3 + uint32_t idx2 = (acc / 85) % 7225; // 85^2 + uint32_t idx3 = acc % 85; + result += table2[idx1]; + result += table2[idx2]; + result.push_back(alph[idx3]); + } + if (padding && !pad) + result.resize(result.size() - padding); + return result; + } + + // Decode Z85 string to bytes, returns success status; outputs bytes in 'out' + inline bool b85decode(const std::string& s, std::vector& out) + { + const auto& alph = _b85alphabet(); + static std::array dec; + static bool init = false; + if (!init) + { + dec.fill(-1); + for (size_t i = 0; i < alph.size(); ++i) + dec[static_cast(alph[i])] = int(i); + init = true; + } + size_t len = s.size(); + size_t padding = (5 - (len % 5)) % 5; + std::string str = s; + str.append(padding, alph[0]); + + out.clear(); + out.reserve((str.size() / 5) * 4); + for (size_t i = 0; i < str.size(); i += 5) + { + uint32_t acc = 0; + for (size_t j = 0; j < 5; ++j) + { + int v = dec[static_cast(str[i + j])]; + if (v < 0) + { + out.clear(); + return false; // bad base85 char + } + acc = acc * 85 + uint32_t(v); + } + out.push_back(uint8_t((acc >> 24) & 0xFF)); + out.push_back(uint8_t((acc >> 16) & 0xFF)); + out.push_back(uint8_t((acc >> 8) & 0xFF)); + out.push_back(uint8_t(acc & 0xFF)); + } + if (padding) + out.resize(out.size() - padding); + return true; + } + + // ------------------------- + // Base58 encoding/decoding (bitcoin-compatible) + // Converted from Python base58 implementation (base58.py) + // ------------------------- + + inline const std::string& b58_alphabet() + { + static const std::string alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + return alphabet; + } + + inline std::string b58encode(const std::vector& data) + { + const auto& alph = b58_alphabet(); + std::string result; + uint64_t num = 0; + for (uint8_t b : data) num = (num << 8) | b; + + while (num > 0) + { + result.insert(result.begin(), alph[num % 58]); + num /= 58; + } + + for (uint8_t b : data) + { + if (b == 0x00) + result.insert(result.begin(), alph[0]); + else + break; + } + return result; + } + + inline bool b58decode(const std::string& s, std::vector& out) + { + const auto& alph = b58_alphabet(); + std::unordered_map index; + for (size_t i = 0; i < alph.size(); ++i) + index[alph[i]] = int(i); + + uint64_t num = 0; + for (char c : s) + { + if (index.find(c) == index.end()) + return false; + num = num * 58 + index[c]; + } + + std::vector tmp; + while (num > 0) + { + tmp.insert(tmp.begin(), static_cast(num & 0xFF)); + num >>= 8; + } + + for (char c : s) + { + if (c == alph[0]) + tmp.insert(tmp.begin(), 0x00); + else + break; + } + out = tmp; + return true; + } + + inline std::string encodeBase58(const std::string& input) noexcept + { + return b58encode(std::vector(input.begin(), input.end())); + } + + inline bool decodeBase58(const std::string& input, std::string& output) + { + std::vector data; + if (!b58decode(input, data)) return false; + output.assign(data.begin(), data.end()); + return true; + } + + // ------------------------- + // Convenience string-based interface + // ------------------------- + + // Encode std::string (raw bytes) to Base16 string + // Returns encoded string + inline std::string encodeBase16(const std::string& input) noexcept + { + std::vector data(input.begin(), input.end()); + return b16encode(data); + } + + // Decode Base16 string to std::string (raw bytes) + // Returns success flag, output in 'output' + inline bool decodeBase16(const std::string& input, std::string& output, bool casefold = false) + { + std::vector data; + if (!b16decode(input, data, casefold)) + return false; + output.assign(data.begin(), data.end()); + return true; + } + + // Encode std::string (raw bytes) to Base85 string + // Returns encoded string + inline std::string encodeBase85(const std::string& input, bool pad = false) noexcept + { + std::vector data(input.begin(), input.end()); + return b85encode(data, pad); + } + + // Decode Base85 string to std::string (raw bytes) + // Returns success flag, output in 'output' + inline bool decodeBase85(const std::string& input, std::string& output) + { + std::vector data; + if (!b85decode(input, data)) + return false; + output.assign(data.begin(), data.end()); + return true; + } + + // ------------------------- + // Base62 encoding/decoding (Python source: base62.py) + // ------------------------- + + inline const std::string& b62_charset_default() + { + static const std::string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + return charset; + } + + inline int b62_char_value(char ch, const std::string& charset) + { + auto pos = charset.find(ch); + if (pos == std::string::npos) + return -1; + return static_cast(pos); + } + + inline std::string b62encode(uint64_t num, const std::string& charset = b62_charset_default()) + { + // From encode() function + std::string result; + if (num == 0) + return "0"; + while (num > 0) + { + result.insert(result.begin(), charset[num % 62]); + num /= 62; + } + return result; + } + + inline std::string encodeBase62(const std::string& input) noexcept + { + // From encodebytes() function + const std::string& charset = b62_charset_default(); + std::vector barray(input.begin(), input.end()); + + int leading_zeros = 0; + for (auto b : barray) + { + if (b != 0) + break; + leading_zeros++; + } + + int n = static_cast(leading_zeros / (charset.size() - 1)); + int r = static_cast(leading_zeros % (charset.size() - 1)); + std::string zero_padding(n, '0'); + zero_padding += std::string(n, charset.back()); + if (r) + zero_padding += "0" + std::string(1, charset[r]); + + if (leading_zeros == static_cast(barray.size())) + return zero_padding; + + uint64_t value = 0; + for (uint8_t b : barray) + value = (value << 8) | b; + + return zero_padding + b62encode(value, charset); + } + + inline uint64_t b62decode(const std::string& s, const std::string& charset = b62_charset_default()) + { + // From decode() function + uint64_t value = 0; + for (char ch : s) + { + int v = b62_char_value(ch, charset); + if (v < 0) + return 0; // indicates failure + value = value * 62 + v; + } + return value; + } + + inline bool decodeBase62(const std::string& input, std::string& output) + { + // From decodebytes() function + const std::string& charset = b62_charset_default(); + size_t i = 0; + std::vector result; + + while (i + 1 < input.size() && input[i] == '0') + { + int count = b62_char_value(input[i + 1], charset); + if (count < 0) + return false; + result.insert(result.end(), count, 0x00); + i += 2; + } + + if (i >= input.size()) + { + output.assign(result.begin(), result.end()); + return true; + } + + uint64_t decoded = b62decode(input.substr(i), charset); + std::vector temp; + while (decoded > 0) + { + temp.push_back(decoded & 0xFF); + decoded >>= 8; + } + std::reverse(temp.begin(), temp.end()); + result.insert(result.end(), temp.begin(), temp.end()); + + output.assign(result.begin(), result.end()); + return true; + } + + // ------------------------- + // Base91 encoding/decoding (Python source: encode/decode functions) + // ------------------------- + + inline const std::string& b91_alphabet() + { + static const std::string alphabet = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[\\]^_`{|}~\""; + return alphabet; + } + + inline bool encodeBase91(const std::string& input, std::string& output) noexcept + { + // From encode(bindata) + const std::string& alphabet = b91_alphabet(); + uint32_t b = 0; + int n = 0; + output.clear(); + + for (uint8_t byte : input) + { + b |= static_cast(byte) << n; + n += 8; + if (n > 13) + { + uint32_t v = b & 8191; + if (v > 88) + { + b >>= 13; + n -= 13; + } + else + { + v = b & 16383; + b >>= 14; + n -= 14; + } + output += alphabet[v % 91]; + output += alphabet[v / 91]; + } + } + if (n) + { + output += alphabet[b % 91]; + if (n > 7 || b > 90) + output += alphabet[b / 91]; + } + return true; + } + + inline bool decodeBase91(const std::string& input, std::string& output) noexcept + { + // From decode(encoded_str) + const std::string& alphabet = b91_alphabet(); + std::unordered_map decode_table; + for (size_t i = 0; i < alphabet.size(); ++i) + decode_table[alphabet[i]] = static_cast(i); + + int v = -1; + uint32_t b = 0; + int n = 0; + std::vector result; + + for (char c : input) + { + if (decode_table.find(c) == decode_table.end()) + continue; + int val = decode_table[c]; + if (v < 0) + v = val; + else + { + v += val * 91; + b |= v << n; + n += (v & 8191) > 88 ? 13 : 14; + while (n >= 8) + { + result.push_back(b & 255); + b >>= 8; + n -= 8; + } + v = -1; + } + } + if (v != -1) + result.push_back((b | (v << n)) & 255); + + output.assign(result.begin(), result.end()); + return true; + } + +} // namespace basecodec + +#endif // BASECODEC_HPP diff --git a/UOHash/dllmain.cpp b/UOHash/dllmain.cpp new file mode 100644 index 0000000..bdd9238 --- /dev/null +++ b/UOHash/dllmain.cpp @@ -0,0 +1,16 @@ +// dllmain.cpp : 定义 DLL 应用程序的入口点。 +#include "pch.h" + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/UOHash/framework.h b/UOHash/framework.h new file mode 100644 index 0000000..80cbbc9 --- /dev/null +++ b/UOHash/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/UOHash/pch.cpp b/UOHash/pch.cpp new file mode 100644 index 0000000..b6fb8f4 --- /dev/null +++ b/UOHash/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: 与预编译标头对应的源文件 + +#include "pch.h" + +// 当使用预编译的头时,需要使用此源文件,编译才能成功。 diff --git a/UOHash/pch.h b/UOHash/pch.h new file mode 100644 index 0000000..9660927 --- /dev/null +++ b/UOHash/pch.h @@ -0,0 +1,13 @@ +// pch.h: 这是预编译标头文件。 +// 下方列出的文件仅编译一次,提高了将来生成的生成性能。 +// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。 +// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。 +// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。 + +#ifndef PCH_H +#define PCH_H + +// 添加要在此处预编译的标头 +#include "framework.h" + +#endif //PCH_H diff --git a/UTextCodec/UTextCodec.cpp b/UTextCodec/UTextCodec.cpp new file mode 100644 index 0000000..c0e48c4 --- /dev/null +++ b/UTextCodec/UTextCodec.cpp @@ -0,0 +1,227 @@ +// UUTextCodec.cpp : 定义静态库的函数。 +// + +#include "UTextCodec.h" +#include +#include +#include +#include +#include +#include +#include + +namespace uns +{ + // ICU中转封装 + static icu_74::UnicodeString fromWString(const std::wstring& wstr) + { +#if WCHAR_MAX == 0xFFFF + return icu_74::UnicodeString(reinterpret_cast(wstr.c_str()), (int32_t)wstr.length()); +#else + std::u16string u16; + for (wchar_t wc : wstr) + { + if (wc <= 0xFFFF) + u16.push_back(static_cast(wc)); + else + { + wc -= 0x10000; + u16.push_back((wc >> 10) + 0xD800); + u16.push_back((wc & 0x3FF) + 0xDC00); + } + } + return icu_74::UnicodeString(reinterpret_cast(u16.c_str()), u16.length()); +#endif + } + + static std::wstring toWString(const icu_74::UnicodeString& ustr) + { + std::wstring wstr; + int32_t i = 0; + while (i < ustr.length()) + { + UChar32 c; + U16_NEXT(ustr.getBuffer(), i, ustr.length(), c); +#if WCHAR_MAX == 0xFFFF + if (c <= 0xFFFF) + wstr.push_back(static_cast(c)); + else + { + c -= 0x10000; + wstr.push_back((c >> 10) + 0xD800); + wstr.push_back((c & 0x3FF) + 0xDC00); + } +#else + wstr.push_back(static_cast(c)); +#endif + } + return wstr; + } + + static icu_74::UnicodeString fromU32String(const std::u32string& str) + { + std::u16string u16; + for (char32_t c : str) + { + if (c <= 0xFFFF) + u16.push_back(static_cast(c)); + else + { + c -= 0x10000; + u16.push_back((c >> 10) + 0xD800); + u16.push_back((c & 0x3FF) + 0xDC00); + } + } + return icu_74::UnicodeString(reinterpret_cast(u16.c_str()), (int32_t)u16.length()); + } + + static std::u32string toU32String(const icu_74::UnicodeString& ustr) + { + std::u32string result; + int32_t i = 0; + while (i < ustr.length()) + { + UChar32 c; + U16_NEXT(ustr.getBuffer(), i, ustr.length(), c); + result.push_back(c); + } + return result; + } +} + +std::wstring UTextCodec::UTF8to16(const std::string& utf8) +{ + icu_74::UnicodeString ustr = icu_74::UnicodeString::fromUTF8(utf8); + return uns::toWString(ustr); +} + +std::string UTextCodec::UTF16to8(const std::wstring& wstr) +{ + icu_74::UnicodeString ustr = uns::fromWString(wstr); + std::string result; + ustr.toUTF8String(result); + return result; +} + +std::u32string UTextCodec::UTF8to32(const std::string& utf8) +{ + icu_74::UnicodeString ustr = icu_74::UnicodeString::fromUTF8(utf8); + return uns::toU32String(ustr); +} + +std::string UTextCodec::UTF32to8(const std::u32string& u32str) +{ + icu_74::UnicodeString ustr = uns::fromU32String(u32str); + std::string result; + ustr.toUTF8String(result); + return result; +} + +std::u32string UTextCodec::WtoUTF32(const std::wstring& wstr) +{ + return uns::toU32String(uns::fromWString(wstr)); +} + +std::wstring UTextCodec::UTF32toW(const std::u32string& u32str) +{ + return uns::toWString(uns::fromU32String(u32str)); +} + +std::wstring UTextCodec::StoW(const std::string& str) +{ +#ifdef _WIN32 + return AtoW(str); +#else + // 默认按 UTF-8 解码 + icu_74::UnicodeString ustr = icu_74::UnicodeString::fromUTF8(str); + return uns::toWString(ustr); +#endif +} + +std::string UTextCodec::WtoS(const std::wstring& wstr) +{ +#ifdef _WIN32 + return WtoA(wstr); +#else + // 默认按 UTF-8 编码 + icu_74::UnicodeString ustr = uns::fromWString(wstr); + std::string result; + ustr.toUTF8String(result); + return result; +#endif +} + +#ifdef _WIN32 + +std::wstring UTextCodec::AtoW(const std::string& ansi, UINT codePage) +{ + // 空指针 + if (ansi.empty()) + return L""; + // 计算长度 + int nNeedSize = MultiByteToWideChar(codePage, 0, ansi.c_str(), -1, NULL, 0); + if (0 == nNeedSize) + return L""; + // 分配空间,转换 + std::wstring strRet(L""); + wchar_t* pRet = new wchar_t[nNeedSize + 1]; + memset(pRet, 0, (nNeedSize + 1) * sizeof(wchar_t)); + if (0 == MultiByteToWideChar(codePage, 0, ansi.c_str(), -1, pRet, nNeedSize)) + { + } + else + strRet = pRet; + delete[]pRet; + return strRet; +} + +std::string UTextCodec::WtoA(const std::wstring& wstr, UINT codePage) +{ + // 空指针输入 + if (wstr.empty()) + return ""; + // 无法计算需要的长度. + int nNeedSize = WideCharToMultiByte(codePage, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL); + if (0 == nNeedSize) + return ""; + // 分配空间,转换. + char* pRet = new char[nNeedSize + 1]; // 虽然返回WideCharToMultiByte的长度是包含 null 字符的长度, 还是多+一个字符. + memset(pRet, 0, nNeedSize + 1); + std::string strRet(""); + if (0 == WideCharToMultiByte(codePage, 0, wstr.c_str(), -1, pRet, nNeedSize, NULL, NULL)) + { + } + else + strRet = pRet; + delete[] pRet; + return strRet; +} + +#endif + +std::string UTextCodec::DetectEncoding() +{ +#ifdef _WIN32 + UINT cp = GetACP(); + return "CP" + std::to_string(cp); +#else + const char* locale = std::getenv("LC_CTYPE"); + if (!locale || std::string(locale).empty()) + locale = std::getenv("LANG"); + if (!locale || std::string(locale).empty()) + return "unknown"; + std::string loc(locale); + // locale 典型格式如 en_US.UTF-8,截取点号后面的编码 + auto pos = loc.find('.'); + if (pos != std::string::npos) + { + std::string encoding = loc.substr(pos + 1); + // 有些编码后面有 @variant,比如 UTF-8@xxx,去掉后面部分 + auto atPos = encoding.find('@'); + if (atPos != std::string::npos) + encoding = encoding.substr(0, atPos); + return encoding; + } + return "unknown"; +#endif +} diff --git a/UTextCodec/UTextCodec.h b/UTextCodec/UTextCodec.h new file mode 100644 index 0000000..02f8d75 --- /dev/null +++ b/UTextCodec/UTextCodec.h @@ -0,0 +1,45 @@ +#pragma once +#include +#ifdef _WIN32 +#include +#if defined(UTEXTCODEC_EXPORTS) +#define UTEXTCODEC_EXPORT __declspec(dllexport) +#else +#define UTEXTCODEC_EXPORT __declspec(dllimport) +#ifdef _DEBUG +#pragma comment(lib, "../x64/Debug/UTextCodecd.lib") +#else +#pragma comment(lib, "../x64/Release/UTextCodec.lib") +#endif +#endif +#else +#define UTEXTCODEC_EXPORT __attribute__((visibility("default"))) +#endif + +class UTEXTCODEC_EXPORT UTextCodec +{ +public: + // UTF-8 ⇄ UTF-16 (wstring) + static std::wstring UTF8to16(const std::string& utf8); + static std::string UTF16to8(const std::wstring& wstr); + + // UTF-8 ⇄ UTF-32 + static std::u32string UTF8to32(const std::string& utf8); + static std::string UTF32to8(const std::u32string& u32str); + + // wstring ⇄ u32string + static std::u32string WtoUTF32(const std::wstring& wstr); + static std::wstring UTF32toW(const std::u32string& u32str); + + // wstring ⇄ string + static std::wstring StoW(const std::string& str); + static std::string WtoS(const std::wstring& wstr); + +#ifdef _WIN32 + // Windows ANSI ⇄ UTF-16 (wstring) + static std::wstring AtoW(const std::string& ansi, UINT codePage = CP_ACP); + static std::string WtoA(const std::wstring& wstr, UINT codePage = CP_ACP); +#endif + + static std::string DetectEncoding(); +}; diff --git a/UTextCodec/UTextCodec.vcxproj b/UTextCodec/UTextCodec.vcxproj new file mode 100644 index 0000000..49e3ffb --- /dev/null +++ b/UTextCodec/UTextCodec.vcxproj @@ -0,0 +1,174 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {a0da5943-b851-42f3-9425-6db8ec22ef34} + UTextCodec + 10.0 + + + + DynamicLibrary + true + v145 + Unicode + + + DynamicLibrary + false + v145 + true + Unicode + + + DynamicLibrary + true + v145 + Unicode + + + DynamicLibrary + false + v145 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(ProjectName)d + + + + Level3 + true + WIN32;_DEBUG;UTEXTCODEC_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp20 + Use + pch.h + + + Windows + true + false + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;UTEXTCODEC_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp20 + Use + pch.h + + + Windows + true + false + + + true + + + + + Level3 + true + _DEBUG;UTEXTCODEC_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp20 + NotUsing + pch.h + + + Windows + true + false + + + true + + + + + Level3 + true + true + true + NDEBUG;UTEXTCODEC_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp20 + NotUsing + pch.h + + + Windows + true + false + + + true + + + + + + + + + + + Create + Create + Create + Create + + + + + + + \ No newline at end of file diff --git a/UTextCodec/UTextCodec.vcxproj.filters b/UTextCodec/UTextCodec.vcxproj.filters new file mode 100644 index 0000000..5682b31 --- /dev/null +++ b/UTextCodec/UTextCodec.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/UTextCodec/dllmain.cpp b/UTextCodec/dllmain.cpp new file mode 100644 index 0000000..bdd9238 --- /dev/null +++ b/UTextCodec/dllmain.cpp @@ -0,0 +1,16 @@ +// dllmain.cpp : 定义 DLL 应用程序的入口点。 +#include "pch.h" + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/UTextCodec/framework.h b/UTextCodec/framework.h new file mode 100644 index 0000000..80cbbc9 --- /dev/null +++ b/UTextCodec/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/UTextCodec/pch.cpp b/UTextCodec/pch.cpp new file mode 100644 index 0000000..b6fb8f4 --- /dev/null +++ b/UTextCodec/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: 与预编译标头对应的源文件 + +#include "pch.h" + +// 当使用预编译的头时,需要使用此源文件,编译才能成功。 diff --git a/UTextCodec/pch.h b/UTextCodec/pch.h new file mode 100644 index 0000000..9660927 --- /dev/null +++ b/UTextCodec/pch.h @@ -0,0 +1,13 @@ +// pch.h: 这是预编译标头文件。 +// 下方列出的文件仅编译一次,提高了将来生成的生成性能。 +// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。 +// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。 +// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。 + +#ifndef PCH_H +#define PCH_H + +// 添加要在此处预编译的标头 +#include "framework.h" + +#endif //PCH_H