Files
blackbird/gateway/src/main.cpp
T
2026-07-25 10:08:02 +08:00

991 lines
29 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <us/HttpServer.hpp>
#include <us/HttpClient.hpp>
#include <us/MyRSA.hpp>
#include <us/Base64.hpp>
#include <us/SHA256.hpp>
#include <us/Sqlite3.hpp>
#include <iostream>
#include <typeinfo>
#include <memory>
#include <csignal>
#include <random>
#include <regex>
#include <boost/json.hpp>
#include <boost/program_options.hpp>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/kdf.h>
#include "ExpBase.hpp"
#include "LocalTime.hpp"
#include "Conf.hpp"
using namespace pmc::net;
using namespace qing;
namespace json = boost::json;
namespace po = boost::program_options;
constexpr const char *_VER = "0.3.0-security";
//////////////////////////////////
#include <sstream>
#include <vector>
#include <string>
std::vector<std::string> split(const std::string& s, char delim) {
std::vector<std::string> tokens;
std::istringstream stream(s);
std::string token;
while (std::getline(stream, token, delim)) {
tokens.push_back(token);
}
return tokens;
}
//////////////////////////////////
// PBKDF2-HMAC-SHA256 password hashing
//////////////////////////////////
class PBKDF2Hasher {
public:
static const int ITERATIONS = 100000;
static const int SALT_LEN = 16;
static const int HASH_LEN = 32;
static std::string hash_password(const std::string& password) {
unsigned char salt[SALT_LEN];
RAND_bytes(salt, SALT_LEN);
unsigned char hash[HASH_LEN];
PKCS5_PBKDF2_HMAC(
password.c_str(), password.size(),
salt, SALT_LEN,
ITERATIONS,
EVP_sha256(),
HASH_LEN, hash
);
// Format: pbkdf2:100000:<hex_salt>:<hex_hash>
std::string salt_hex = bytes_to_hex(salt, SALT_LEN);
std::string hash_hex = bytes_to_hex(hash, HASH_LEN);
return "pbkdf2:" + std::to_string(ITERATIONS) + ":" + salt_hex + ":" + hash_hex;
}
static bool verify_password(const std::string& password, const std::string& stored) {
// Parse stored hash
if (stored.substr(0, 7) != "pbkdf2:") {
return false;
}
auto parts = split(stored, ':');
if (parts.size() != 4) return false;
int iterations = std::stoi(parts[1]);
auto salt = hex_to_bytes(parts[2]);
auto expected_hash = hex_to_bytes(parts[3]);
unsigned char hash[HASH_LEN];
PKCS5_PBKDF2_HMAC(
password.c_str(), password.size(),
salt.data(), salt.size(),
iterations,
EVP_sha256(),
HASH_LEN, hash
);
// Constant-time comparison
if (expected_hash.size() != (size_t)HASH_LEN) return false;
unsigned char diff = 0;
for (int i = 0; i < HASH_LEN; i++) {
diff |= hash[i] ^ expected_hash[i];
}
return diff == 0;
}
private:
static std::string bytes_to_hex(const unsigned char* data, size_t len) {
static const char hex[] = "0123456789abcdef";
std::string result;
result.reserve(len * 2);
for (size_t i = 0; i < len; i++) {
result.push_back(hex[data[i] >> 4]);
result.push_back(hex[data[i] & 0x0F]);
}
return result;
}
static std::vector<unsigned char> hex_to_bytes(const std::string& hex_str) {
std::vector<unsigned char> bytes;
bytes.reserve(hex_str.size() / 2);
for (size_t i = 0; i < hex_str.size(); i += 2) {
auto hi = hex_char_to_nibble(hex_str[i]);
auto lo = hex_char_to_nibble(hex_str[i + 1]);
bytes.push_back((hi << 4) | lo);
}
return bytes;
}
static unsigned char hex_char_to_nibble(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
return 0;
}
};
//////////////////////////////////
// Password strength validator
//////////////////////////////////
class PasswordValidator {
public:
struct Result {
bool valid;
std::string message;
};
static Result validate(const std::string& password) {
if (password.size() < 8) {
return {false, "Password must be at least 8 characters"};
}
if (password.size() > 128) {
return {false, "Password too long (max 128)"};
}
bool has_upper = false, has_lower = false, has_digit = false, has_special = false;
for (char c : password) {
if (std::isupper(c)) has_upper = true;
else if (std::islower(c)) has_lower = true;
else if (std::isdigit(c)) has_digit = true;
else has_special = true;
}
int categories = has_upper + has_lower + has_digit + has_special;
if (categories < 3) {
return {false, "Password must contain at least 3 of: uppercase, lowercase, digit, special character"};
}
return {true, "OK"};
}
};
//////////////////////////////////
// IP Rate Limiter (in-memory)
//////////////////////////////////
#include <unordered_map>
#include <mutex>
#include <chrono>
class RateLimiter {
public:
struct AttemptInfo {
int count;
std::chrono::steady_clock::time_point first_attempt;
};
bool check_and_record(const std::string& ip, int max_attempts = 5, int window_seconds = 300) {
std::lock_guard<std::mutex> lock(mtx);
auto now = std::chrono::steady_clock::now();
auto& info = attempts[ip];
// Reset if window expired
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - info.first_attempt).count();
if (elapsed > window_seconds) {
info.count = 0;
info.first_attempt = now;
}
info.count++;
// Cleanup old entries periodically
if (attempts.size() > 10000) {
for (auto it = attempts.begin(); it != attempts.end(); ) {
auto e = std::chrono::duration_cast<std::chrono::seconds>(now - it->second.first_attempt).count();
if (e > window_seconds) it = attempts.erase(it);
else ++it;
}
}
return info.count <= max_attempts;
}
private:
std::unordered_map<std::string, AttemptInfo> attempts;
std::mutex mtx;
};
//////////////////////////////////
class DatabaseException: public ExceptionBase {
public:
using ExceptionBase::ExceptionBase;
std::string build_message() const override {
return "[Database]" + ExceptionBase::build_message();
}
};
class HttpException: public ExceptionBase {
public:
HttpException(int code, const std::string& msg,
int http_status, const std::string& url,
const std::source_location& loc = std::source_location::current())
: ExceptionBase(code, msg, loc)
, m_http_status(http_status)
, m_url(url) {}
std::string build_message() const override {
return "[Http]" + ExceptionBase::build_message();
}
int status() const {
return m_http_status;
}
const std::string& url() const {
return m_url;
}
private:
int m_http_status;
std::string m_url;
};
void db_init();
void registerRoutes(HttpServer &server);
std::string generate_captcha_text();
bool verify_captcha(const std::string& token, const std::string& answer);
http::response<http::string_body> wrapperHttp(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params,
HttpRequestHandler _main);
http::response<http::string_body> handleProxy(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params,
const char *method
);
http::response<http::string_body> handleLogin(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params);
http::response<http::string_body> handleRegister(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params);
http::response<http::string_body> handleCaptcha(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params);
/* headers can't processed */
bool is_special_header(const std::string &header);
/* configure items */
static std::string _ADDR;
static int _PORT;
static bool _TLS = false;
static std::string _DEFT_URL;
static std::vector<std::string> _OPEN_URL;
static std::string _DB_PATH;
/* instance items */
static std::unique_ptr<HttpServer> _SERVE;
static std::unique_ptr<MyRSA::Generator> _KEYGEN;
static std::unique_ptr<Sqlite3> _SQLDB;
static RateLimiter _LOGIN_RATE;
static RateLimiter _REGISTER_RATE;
/* CAPTCHA store: token -> answer (in-memory, with expiry) */
#include <unordered_map>
#include <mutex>
struct CaptchaEntry {
std::string answer;
std::chrono::steady_clock::time_point created;
};
static std::unordered_map<std::string, CaptchaEntry> _CAPTCHA_STORE;
static std::mutex _CAPTCHA_MTX;
std::string generate_captcha_text() {
static const char chars[] = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
// thread_local:每个线程独立 rng 实例,避免多线程并发调用 dist(rng) 时的数据竞争
// (static 变量初始化本身线程安全,但 mt19937::operator() 修改内部状态非线程安全)
thread_local std::mt19937 rng(std::random_device{}());
thread_local std::uniform_int_distribution<int> dist(0, sizeof(chars) - 2);
std::string text;
for (int i = 0; i < 5; i++) {
text += chars[dist(rng)];
}
return text;
}
bool verify_captcha(const std::string& token, const std::string& answer) {
std::lock_guard<std::mutex> lock(_CAPTCHA_MTX);
auto it = _CAPTCHA_STORE.find(token);
if (it == _CAPTCHA_STORE.end()) return false;
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::steady_clock::now() - it->second.created).count();
if (elapsed > 300) { // 5 min expiry
_CAPTCHA_STORE.erase(it);
return false;
}
bool match = (it->second.answer == answer);
_CAPTCHA_STORE.erase(it); // one-time use
return match;
}
void signalHandler(int signum) {
std::cout << "Receive close signal, prepare to shutdown...\n";
if (_SERVE) {
_SERVE->stop();
}
}
void set_signal_action() {
struct sigaction sa;
sa.sa_handler = signalHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
}
int main(int argc, char **argv) {
po::options_description desc("cpp language web gateway");
desc.add_options()
("help,h", "output help message")
("version,v", "output version message")
("input,i", po::value<std::string>(), "input a configure text file")
;
po::variables_map vm;
{
try {
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
}
catch (const std::exception &e) {
std::cerr << "ERROR: " << e.what() << std::endl;
std::cerr << "using --help to check options message" << std::endl;
throw e;
}
}
if (vm.count("help")) {
std::cout << desc << std::endl;
return 0;
}
if (!vm.count("input")) {
std::cerr << "Please input configure file via -i" << std::endl;
return 0;
}
{
try {
ConfIni cf(vm["input"].as<std::string>());
_ADDR = cf.get<std::string>("Server.address", "");
_PORT = cf.get<int> ("Server.port", -1);
_TLS = cf.get<bool> ("Server.tls", false);
_DEFT_URL = cf.get<std::string>("Proxy.default", "");
_OPEN_URL = split(cf.get<std::string>("Proxy.opened", ""), ';');
_DB_PATH = cf.get<std::string>("Database.path", "");
}
catch (ConfIni::IniConfigureFileParseException &e) {
std::cerr << "ERROR: " << e.what() << std::endl;
std::cerr << "ini format input file error" << std::endl;
return 1;
}
}
if (_ADDR == "" || _PORT == -1) {
std::cerr << "Please setup addree and port in Serve section in configure file";
std::cerr << std::endl;
return 0;
}
// 根据 Server.tls 选择 HTTPS(带证书)或 HTTP(无证书)构造函数
// HTTP 模式用于 WAF 终止 TLS 后的内部转发链路
if (_TLS) {
_SERVE = std::make_unique<HttpServer> (_ADDR, _PORT, "full_chain.crt", "server.key", 4);
} else {
_SERVE = std::make_unique<HttpServer> (_ADDR, _PORT, 4);
}
_KEYGEN = std::make_unique<MyRSA::Generator> ();
_SQLDB = std::make_unique<Sqlite3> (_DB_PATH);
db_init();
registerRoutes(*_SERVE);
set_signal_action();
std::cout << "Gateway " << _VER << " started (PBKDF2 + CAPTCHA + RateLimit)" << std::endl;
_SERVE->start();
_SERVE->run();
}
void db_init() {
std::string sql = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Users'";
auto flag = false;
{
auto stmt = _SQLDB->stmt(sql);
if (stmt.step()) {
auto num = stmt.read_int(0);
if (num == 0) {
flag = true;
}
}
}
if (flag) {
std::cout << "Start database init..." << std::endl;
std::string sql_crtt =
"CREATE TABLE Users (\n"
"id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
"email TEXT UNIQUE NOT NULL,\n"
"password_hash TEXT NOT NULL,\n"
"create_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n"
"last_login DATETIME,\n"
"is_active BOOLEAN DEFAULT 1\n"
");\n\n"
"CREATE INDEX idx_users_email ON users(email);\n";
_SQLDB->exec(sql_crtt);
// No hardcoded admin - first registered user becomes admin
std::cout << "done (no default admin - register manually)" << std::endl;
}
else {
std::cout << "The database already init" << std::endl;
}
}
void registerRoutes(HttpServer &server) {
server.post("/captcha", [](const auto& req, const auto& params) {
return handleCaptcha(req, params);
});
server.post("/login", [](
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params
) -> http::response<http::string_body> {
return wrapperHttp(req, params, [](
const auto& req, const auto& params
) {
return handleLogin(req, params);
});
});
server.post("/register", [](const auto& req, const auto& params) {
return handleRegister(req, params);
});
server.get("*", [](const auto& req, const auto& params) {
return handleProxy(req, params, "get");
});
server.post("*", [](const auto& req, const auto& params) {
return handleProxy(req, params, "post");
});
server.options("*", [](const auto& req, const auto& params) {
pmc::net::http::response<pmc::net::http::string_body> res;
res.result(pmc::net::http::status::ok);
res.set(pmc::net::http::field::access_control_allow_origin, "*");
res.set(pmc::net::http::field::access_control_allow_methods, "GET, POST, OPTIONS");
res.set(pmc::net::http::field::access_control_allow_headers, "Content-Type, Authorization");
res.set(pmc::net::http::field::access_control_max_age, "86400");
res.body() = "";
res.content_length(0);
res.prepare_payload();
return res;
});
}
http::response<http::string_body> wrapperHttp(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params,
HttpRequestHandler _main)
try {
return _main(req, params);
}
catch (const std::exception& e) {
http::response<http::string_body> res;
res.version(req.version());
const auto& ti = typeid(e);
// 详细错误信息(ti.name() 含 mangled 类型名、e.what() 含内部细节)暂时以注释方式保留
// 原因:此处为 wrapperHttp 外层 catch,会捕获 handler 内未处理的 std::exception
// 透传到客户端会泄露 C++ 类型信息与内部路径(P0-3 遗漏点,已补全)
// 排障:完整信息仍输出到服务端 stderr
std::cerr << "[wrapperHttp std::exception] <" << ti.name() << "> " << e.what() << std::endl;
json::value jv_r = {
{"success", false},
/* {"message", std::string("<") + ti.name() + "> " + e.what()}, */ // 泄露源码信息,已禁用
{"message", "Internal Server Error"}
};
res.set(http::field::content_type, "application/json");
res.result(http::status::internal_server_error);
res.body() = json::serialize(jv_r);
res.prepare_payload();
return res;
}
// CAPTCHA endpoint: generates a random text, stores it, returns a simple token
http::response<http::string_body> handleCaptcha(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params)
{
http::response<http::string_body> res;
res.version(req.version());
std::string text = generate_captcha_text();
// Generate a token for this captcha
unsigned char token_bytes[16];
RAND_bytes(token_bytes, 16);
std::string token;
static const char hex[] = "0123456789abcdef";
for (int i = 0; i < 16; i++) {
token += hex[token_bytes[i] >> 4];
token += hex[token_bytes[i] & 0x0F];
}
{
std::lock_guard<std::mutex> lock(_CAPTCHA_MTX);
_CAPTCHA_STORE[token] = {text, std::chrono::steady_clock::now()};
}
json::value jv = {
{"token", token},
{"text", text}
};
res.set(http::field::content_type, "application/json");
res.result(http::status::ok);
res.body() = json::serialize(jv);
res.prepare_payload();
return res;
}
http::response<http::string_body> handleLogin(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params)
{
http::response<http::string_body> res;
res.version(req.version());
// IP rate limit check
std::string client_ip = "";
auto it_host = req.base().find("X-Forwarded-For");
if (it_host != req.base().end()) {
client_ip = std::string(it_host->value());
} else {
client_ip = "direct";
}
if (!_LOGIN_RATE.check_and_record(client_ip, 5, 300)) {
json::value jv_r = {
{"success", false},
{"message", "Too many login attempts. Please try again later."}
};
res.set(http::field::content_type, "application/json");
res.result(http::status::too_many_requests);
res.body() = json::serialize(jv_r);
res.prepare_payload();
return res;
}
try {
json::value jv;
try {
jv = json::parse(req.body());
}
catch (const boost::wrapexcept<boost::system::system_error>& e) {
THROW_EX_CUSTOM(HttpException, 2001, "Invalid_json_Argument",
400, "/login");
}
if (!jv.is_object()) {
THROW_EX_CUSTOM(HttpException, 2002, "Invalid_json_object_Argument",
400, "/login");
}
auto& obj = jv.as_object();
auto username = std::string(obj.at("username").as_string());
auto password = std::string(obj.at("password").as_string());
// CAPTCHA verification
auto captcha_it = obj.find("captcha_token");
auto captcha_ans_it = obj.find("captcha_answer");
if (captcha_it != obj.end() && captcha_ans_it != obj.end()) {
std::string ct = std::string(captcha_it->value().as_string());
std::string ca = std::string(captcha_ans_it->value().as_string());
if (!verify_captcha(ct, ca)) {
THROW_EX_CUSTOM(HttpException, 2201, "Invalid CAPTCHA",
400, "/login");
}
}
// Fetch stored hash - support both PBKDF2 and legacy SHA256
std::string sql = "SELECT password_hash FROM Users WHERE email=?;";
auto stmt = _SQLDB->stmt(sql);
stmt.bind_text(1, username);
std::string stored_hash = "";
if (stmt.step()) {
stored_hash = std::string(reinterpret_cast<const char*>(stmt.read_text(0)));
}
bool auth_ok = false;
if (!stored_hash.empty()) {
if (stored_hash.substr(0, 7) == "pbkdf2:") {
// New PBKDF2 hash
auth_ok = PBKDF2Hasher::verify_password(password, stored_hash);
} else {
// Legacy SHA256 hash - verify then auto-migrate
auto legacy_hash = SHA256::sha256(password);
if (legacy_hash == stored_hash) {
auth_ok = true;
// Auto-migrate to PBKDF2
auto new_hash = PBKDF2Hasher::hash_password(password);
std::string sql_migrate = "UPDATE Users SET password_hash=? WHERE email=?;";
auto stmt_mig = _SQLDB->stmt(sql_migrate);
stmt_mig.bind_text(1, new_hash);
stmt_mig.bind_text(2, username);
stmt_mig.step();
std::cout << "Migrated password hash for " << username << " to PBKDF2" << std::endl;
}
}
}
if (!auth_ok) {
THROW_EX_CUSTOM(HttpException, 2101, "Invalid Username or Password",
401, "/login");
}
// Update last_login
std::string sql_update = "UPDATE Users SET last_login=datetime('now','localtime') WHERE email=?;";
auto stmt_upd = _SQLDB->stmt(sql_update);
stmt_upd.bind_text(1, username);
stmt_upd.step();
auto _PRIV = MyRSA::Private_Key::from_pem(_KEYGEN->get_private_key_pem());
int keep_time = 3600;
json::value jv_t = {
{"user", username},
{"created_at", LocalTime::getCurrent()},
{"keep_time", keep_time}
};
std::string data = json::serialize(jv_t);
std::string token = Base64::base64_encode(data);
std::string sign = _PRIV.Sign(data);
json::value jv_rr = {
{"success", true},
{"data", json::object{{"token", token + "_" + sign}}}
};
res.set("Connection", "close");
res.result(http::status::ok);
res.body() = json::serialize(jv_rr);
}
catch (const HttpException& e) {
// 详细错误信息(e.what() 含源码路径/行号/C++ 模板类型)暂时以注释方式保留
// 原因:黑盒检测发现透传到客户端会泄露内部实现,便于攻击者定位代码(P0-3)
// 排障:完整信息仍输出到服务端 stderr
std::cerr << "[handleLogin HttpException] " << e.what() << std::endl;
json::value jv_r = {
{"success", false},
/* {"message", e.what()}, */ // 泄露源码信息,已禁用
{"message", "Invalid Username or Password"}
};
res.set(http::field::content_type, "application/json");
res.result(e.status());
res.body() = json::serialize(jv_r);
}
res.prepare_payload();
return res;
}
http::response<http::string_body> handleRegister(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params)
{
http::response<http::string_body> res;
res.version(req.version());
// IP rate limit check
std::string client_ip = "direct";
auto it_host = req.base().find("X-Forwarded-For");
if (it_host != req.base().end()) {
client_ip = std::string(it_host->value());
}
if (!_REGISTER_RATE.check_and_record(client_ip, 3, 600)) {
json::value jv_r = {
{"success", false},
{"message", "Too many registration attempts. Please try again later."}
};
res.set(http::field::content_type, "application/json");
res.result(http::status::too_many_requests);
res.body() = json::serialize(jv_r);
res.prepare_payload();
return res;
}
try {
auto jv = json::parse(req.body());
if (!jv.is_object()) {
throw std::runtime_error("400");
}
auto& obj = jv.as_object();
auto username = std::string(obj.at("username").as_string());
auto password = std::string(obj.at("password").as_string());
// Strong password check
auto pw_result = PasswordValidator::validate(password);
if (!pw_result.valid) {
json::value jv_r = {
{"success", false},
{"message", pw_result.message}
};
res.set(http::field::content_type, "application/json");
res.result(http::status::bad_request);
res.body() = json::serialize(jv_r);
res.prepare_payload();
return res;
}
// Email format check
std::regex email_regex(R"(^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$)");
if (!std::regex_match(username, email_regex)) {
json::value jv_r = {
{"success", false},
{"message", "Invalid email format"}
};
res.set(http::field::content_type, "application/json");
res.result(http::status::bad_request);
res.body() = json::serialize(jv_r);
res.prepare_payload();
return res;
}
// CAPTCHA verification
auto captcha_it = obj.find("captcha_token");
auto captcha_ans_it = obj.find("captcha_answer");
if (captcha_it != obj.end() && captcha_ans_it != obj.end()) {
std::string ct = std::string(captcha_it->value().as_string());
std::string ca = std::string(captcha_ans_it->value().as_string());
if (!verify_captcha(ct, ca)) {
THROW_EX_CUSTOM(HttpException, 2201, "Invalid CAPTCHA",
400, "/register");
}
}
auto password_hash = PBKDF2Hasher::hash_password(password);
std::string sql = "INSERT INTO Users (email, password_hash, last_login) VALUES (?, ?, datetime('now', 'localtime'));";
auto stmt = _SQLDB->stmt(sql);
stmt.bind_text(1, username);
stmt.bind_text(2, password_hash);
stmt.step();
json::value jv_r = {{"success", true}};
res.result(http::status::ok);
res.body() = json::serialize(jv_r);
}
catch (const HttpException& e) {
// 详细错误信息(e.what() 含源码路径/行号/C++ 模板类型)暂时以注释方式保留(同 handleLogin)
std::cerr << "[handleRegister HttpException] " << e.what() << std::endl;
json::value jv_r = {
{"success", false},
/* {"message", e.what()}, */ // 泄露源码信息,已禁用
{"message", "Registration failed"}
};
res.set(http::field::content_type, "application/json");
res.result(e.status());
res.body() = json::serialize(jv_r);
}
catch (const std::exception& e) {
const auto& ti = typeid(e);
// 详细错误信息(ti.name() 含 mangled 类型名、e.what() 含内部细节)暂时以注释方式保留
// 原因:mangled 类型名会确认技术栈与 STL 容器,e.what() 可能泄露内部路径(P0-3)
// 排障:完整信息仍输出到服务端 stderr
std::cerr << "[handleRegister std::exception] <" << ti.name() << "> " << e.what() << std::endl;
res.set(http::field::content_type, "application/json");
res.result(http::status::internal_server_error);
json::value jv_r = {
{"success", false},
/* {"err_name", ti.name()}, // 泄露 C++ 类型信息,已禁用 */
/* {"err_message", e.what()} // 泄露内部细节,已禁用 */
{"message", "Internal Server Error"}
};
res.body() = json::serialize(jv_r);
}
res.prepare_payload();
return res;
}
http::response<http::string_body> handleProxy(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params,
const char *method)
{
http::response<http::string_body> res;
res.version(req.version());
try {
auto _PUBL = MyRSA::Public_Key::from_pem(_KEYGEN->get_public_key_pem());
std::string t_url = "";
std::string url = "";
std::string auth = "";
json::value jv_t;
{
auto flag = false;
auto it = req.base().find("Target-Url");
if (it != req.base().end()) {
t_url = req["Target-Url"];
}
it = req.base().find("target-url");
if (it != req.base().end()) {
t_url = req["target-url"];
}
if (t_url == "") {
t_url = _DEFT_URL;
flag = true;
}
url = t_url;
url += "/";
url += req.target();
it = req.base().find("Authorization");
if (it == req.base().end() && !flag) {
throw std::runtime_error("401");
}
std::string raw = req["Authorization"];
if (raw.substr(0, 7) == "Bearer ") {
raw = raw.substr(7, raw.size() - 7);
size_t idx = raw.find("_");
if (idx == std::string::npos) {
throw std::runtime_error("Header 400 Bad Request");
}
std::string data = raw.substr(0, idx);
std::string sign = raw.substr(idx+1, raw.size()-idx-1);
auth = Base64::base64_decode_to_string(data);
if (!_PUBL.Verify(auth, sign)) {
throw std::runtime_error("Header 401 Unauthorized");
}
}
if (!flag) {
jv_t = json::parse(auth);
json::object& obj = jv_t.as_object();
auto t = json::value_to<std::string>(obj["created_at"]);
auto keep_time_val = json::value_to<int>(obj["keep_time"]);
auto created_tp = LocalTime::parse(t);
if (created_tp) {
auto now = std::chrono::system_clock::now();
auto expire_tp = *created_tp + std::chrono::seconds(keep_time_val);
if (now > expire_tp) {
THROW_EX_CUSTOM(HttpException, 2000, "Authorization Expired",
401, "*");
}
}
}
}
{
bool flag = false;
for (auto& s: _OPEN_URL) {
if (s == t_url) {
flag = true;
break;
}
}
if (!flag) {
throw std::runtime_error("403");
}
}
auto callback = [&res](const auto& ret) {
res.body() += ret;
};
auto client = HttpClient();
for (auto const& field: req) {
if (is_special_header(field.name_string()))
continue;
auto h = std::string();
h += field.name_string();
h += ": ";
h += field.value();
client.set_header(h);
}
if (method == "get")
client.get_stream_sync(url, callback);
else if (method == "post") {
std::string payload = req.body();
client.post_stream_sync(url, payload, callback);
}
for (const auto& pair: client.get_response_headers() ) {
res.set(pair.first, pair.second);
}
res.set("Connection", "close");
res.result(client.get_response_code());
}
catch (const std::exception& e) {
const auto& ti = typeid(e);
// 详细错误信息(ti.name() 含 mangled 类型名、e.what() 含内部细节)暂时以注释方式保留
// 原因:mangled 类型名会确认技术栈与 STL 容器,e.what() 可能泄露内部路径(P0-3)
// 排障:完整信息仍输出到服务端 stderr
std::cerr << "[handleProxy std::exception] <" << ti.name() << "> " << e.what() << std::endl;
res.set(http::field::content_type, "application/json");
res.result(http::status::internal_server_error);
json::value jv_r = {
{"success", false},
/* {"err_name", ti.name()}, // 泄露 C++ 类型信息,已禁用 */
/* {"err_message", e.what()} // 泄露内部细节,已禁用 */
{"message", "Internal Server Error"}
};
res.body() = json::serialize(jv_r);
}
res.prepare_payload();
return res;
}
bool is_special_header(const std::string &header) {
return header == "Host" || header == "connection"
|| header == "Accept-Encoding";
}