146 lines
4.6 KiB
C++
146 lines
4.6 KiB
C++
#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<unsigned char>(s[start])))
|
||
++start;
|
||
if (start == s.size())
|
||
return "";
|
||
|
||
size_t end = s.size() - 1;
|
||
while ((end > start) && std::isspace(static_cast<unsigned char>(s[end])))
|
||
--end;
|
||
return s.substr(start, end - start + 1);
|
||
}
|
||
|
||
static inline std::vector<std::string> SplitByComma(const std::string& raw)
|
||
{
|
||
std::vector<std::string> 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<std::string> CORSProcessor::ParseHeaderList(const std::string& raw_headers)
|
||
{
|
||
std::set<std::string> 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<std::string> CORSProcessor::ParseMethodList(const std::string& raw_headers)
|
||
{
|
||
std::set<std::string> 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<std::string>() : 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> CORSProcessor::SharedPtr()
|
||
{
|
||
return std::make_shared<CORSProcessor>();
|
||
} |