Files
blackbird/captcha2/test_gen.cpp
T
qing 546394b681 feat: captcha C++20 重写 + 安全审计修复 (P0/P1/P2/P3, 10/11 已修复)
captcha2 — C++20 验证码服务(FreeType + stb_image_write):
- 完整实现: 图片生成、CaptchaStore、RateLimiter、pymsg RPC 路由
- 协议完全兼容 Python 原版,gateway 零改动

审计修复 (captcha2_SECURITY_AUDIT_20260809.md):
- P0-1: 存储答案小写化 (对齐 Python 版语义)
- P1-1: gateway 按 IP 限流 10/min + captcha2 全局限流 600/min 兜底
- P2-1: generate_text mt19937 → std::random_device
- P2-2: ::tolower UB → unsigned char lambda
- P3-1: parse_int 辅助函数, stoi 异常可诊断
- P3-2: 显式 #include <iomanip>
- P3-3: 补齐 CAPTCHA_LEN/TTL/RATE_LIMIT 环境变量
- P3-4: LogC 日志超时 5.0→1.0s
- P3-5: 限流/满容抛异常, gateway 映射 429/503
- P3-6: 清理死代码

集成:
- core/init.txt: captcha → captcha2
- confs configs/captcha.ini: 新增 max_store/rate_limit_per_min
- docs: 开发方案、安全审计报告、交叉引用更新
2026-08-09 04:26:09 +00:00

220 lines
7.3 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.
/*
* test_gen.cpp — 快速验证图片生成(不依赖 confs/RpcServer)
* 用法: make -f Makefile.test && ./test_gen.bin
*/
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "inc/stb_image_write.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mutex>
#include <random>
#include <sstream>
#include <vector>
// ── 复制 main.cpp 中的核心代码(简化为测试版)───────────────────────
static constexpr int kImgWidth = 160;
static constexpr int kImgHeight = 50;
static constexpr int kImgChannels = 3;
struct RGB { uint8_t r, g, b; };
static thread_local std::mt19937 t_rng(std::random_device{}());
static RGB random_color() {
std::uniform_int_distribution<int> d(0, 255);
return {static_cast<uint8_t>(d(t_rng)), static_cast<uint8_t>(d(t_rng)), static_cast<uint8_t>(d(t_rng))};
}
static std::string generate_text(int len) {
static const char kChars[] = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
static constexpr size_t kNumChars = sizeof(kChars) - 1;
thread_local std::mt19937 rng(std::random_device{}());
std::uniform_int_distribution<size_t> dist(0, kNumChars - 1);
std::string text;
for (int i = 0; i < len; ++i) text.push_back(kChars[dist(rng)]);
return text;
}
static inline uint8_t* pixel_at(std::vector<uint8_t>& pix, int x, int y) {
return &pix[(y * kImgWidth + x) * kImgChannels];
}
static std::vector<uint8_t> rotate_bitmap(
const FT_Bitmap& src, double angle_deg,
int& out_w, int& out_h, int& offset_x, int& offset_y)
{
double rad = angle_deg * M_PI / 180.0;
double cos_a = std::cos(rad), sin_a = std::sin(rad);
int sw = static_cast<int>(src.width), sh = static_cast<int>(src.rows);
double corners[4][2] = {
{0.0, 0.0}, {double(sw), 0.0}, {double(sw), double(sh)}, {0.0, double(sh)}
};
double min_x = 1e9, max_x = -1e9, min_y = 1e9, max_y = -1e9;
for (auto& c : corners) {
double rx = c[0] * cos_a - c[1] * sin_a;
double ry = c[0] * sin_a + c[1] * cos_a;
if (rx < min_x) min_x = rx;
if (rx > max_x) max_x = rx;
if (ry < min_y) min_y = ry;
if (ry > max_y) max_y = ry;
}
out_w = static_cast<int>(std::ceil(max_x - min_x));
out_h = static_cast<int>(std::ceil(max_y - min_y));
offset_x = static_cast<int>(std::floor(min_x));
offset_y = static_cast<int>(std::floor(min_y));
std::vector<uint8_t> out(out_w * out_h, 0);
for (int dy = 0; dy < out_h; ++dy) {
for (int dx = 0; dx < out_w; ++dx) {
double sx = (dx + min_x) * cos_a + (dy + min_y) * sin_a;
double sy = -(dx + min_x) * sin_a + (dy + min_y) * cos_a;
int six = static_cast<int>(std::floor(sx));
int siy = static_cast<int>(std::floor(sy));
if (six >= 0 && six < sw && siy >= 0 && siy < sh)
out[dy * out_w + dx] = src.buffer[siy * src.pitch + six];
}
}
return out;
}
static void composite_char(
std::vector<uint8_t>& pixels,
const std::vector<uint8_t>& glyph, int gw, int gh,
int dst_x, int dst_y, const RGB& color)
{
for (int gy = 0; gy < gh; ++gy) {
for (int gx = 0; gx < gw; ++gx) {
uint8_t alpha = glyph[gy * gw + gx];
if (alpha == 0) continue;
int px = dst_x + gx, py = dst_y + gy;
if (px < 0 || px >= kImgWidth || py < 0 || py >= kImgHeight) continue;
auto* p = pixel_at(pixels, px, py);
float a = alpha / 255.0f;
p[0] = static_cast<uint8_t>(p[0] * (1 - a) + color.r * a);
p[1] = static_cast<uint8_t>(p[1] * (1 - a) + color.g * a);
p[2] = static_cast<uint8_t>(p[2] * (1 - a) + color.b * a);
}
}
}
static void draw_line(std::vector<uint8_t>& pixels, int x0, int y0, int x1, int y1, const RGB& color) {
int dx = std::abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -std::abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
while (true) {
if (x0 >= 0 && x0 < kImgWidth && y0 >= 0 && y0 < kImgHeight) {
auto* p = pixel_at(pixels, x0, y0);
p[0] = color.r; p[1] = color.g; p[2] = color.b;
}
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; }
if (e2 <= dx) { err += dx; y0 += sy; }
}
}
static void set_pixel(std::vector<uint8_t>& pixels, int x, int y, const RGB& color) {
if (x >= 0 && x < kImgWidth && y >= 0 && y < kImgHeight) {
auto* p = pixel_at(pixels, x, y);
p[0] = color.r; p[1] = color.g; p[2] = color.b;
}
}
static std::vector<uint8_t> generate_image(const std::string& text, const std::string& font_path) {
std::vector<uint8_t> pixels(kImgWidth * kImgHeight * kImgChannels);
auto bg = random_color();
for (int i = 0; i < kImgWidth * kImgHeight; ++i) {
pixels[i * 3 + 0] = bg.r;
pixels[i * 3 + 1] = bg.g;
pixels[i * 3 + 2] = bg.b;
}
FT_Library ft;
if (FT_Init_FreeType(&ft) != 0) throw std::runtime_error("FreeType init failed");
FT_Face face;
if (FT_New_Face(ft, font_path.c_str(), 0, &face) != 0) {
FT_Done_FreeType(ft);
throw std::runtime_error("Font not found: " + font_path);
}
std::uniform_int_distribution<int> angle_dist(-30, 30);
std::uniform_int_distribution<int> size_dist(20, 38);
std::uniform_int_distribution<int> y_dist(2, 12);
std::uniform_int_distribution<int> x_jitter(-3, 3);
for (size_t i = 0; i < text.size(); ++i) {
int char_size = size_dist(t_rng);
FT_Set_Pixel_Sizes(face, 0, char_size);
if (FT_Load_Char(face, text[i], FT_LOAD_RENDER) != 0) continue;
auto& bmp = face->glyph->bitmap;
if (bmp.buffer == nullptr || bmp.width == 0 || bmp.rows == 0) continue;
auto fg = random_color();
int angle = angle_dist(t_rng);
int y = y_dist(t_rng);
int x = 15 + static_cast<int>(i) * 32 + x_jitter(t_rng);
int rw, rh, ox, oy;
auto rotated = rotate_bitmap(bmp, angle, rw, rh, ox, oy);
int cx = x - rw / 2, cy = y - rh / 2;
composite_char(pixels, rotated, rw, rh, cx, cy, fg);
}
FT_Done_Face(face);
FT_Done_FreeType(ft);
std::uniform_int_distribution<int> xd(0, kImgWidth - 1);
std::uniform_int_distribution<int> yd(0, kImgHeight - 1);
for (int i = 0; i < 4; ++i)
draw_line(pixels, xd(t_rng), yd(t_rng), xd(t_rng), yd(t_rng), random_color());
for (int i = 0; i < 50; ++i)
set_pixel(pixels, xd(t_rng), yd(t_rng), random_color());
std::vector<uint8_t> png_buf;
stbi_write_png_to_func(
[](void* ctx, void* data, int size) {
auto* buf = static_cast<std::vector<uint8_t>*>(ctx);
buf->insert(buf->end(), static_cast<uint8_t*>(data), static_cast<uint8_t*>(data) + size);
}, &png_buf, kImgWidth, kImgHeight, kImgChannels, pixels.data(), kImgWidth * kImgChannels);
return png_buf;
}
// ── main — 生成测试 PNG ──────────────────────────────────────────────────
int main(int argc, char* argv[]) {
std::string font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf";
std::string text = "AB3X";
if (argc >= 2) text = argv[1];
if (argc >= 3) font_path = argv[2];
std::cout << "文字: " << text << std::endl;
std::cout << "字体: " << font_path << std::endl;
try {
auto png = generate_image(text, font_path);
std::string fname = "test_output_" + text + ".png";
std::ofstream out(fname, std::ios::binary);
out.write(reinterpret_cast<const char*>(png.data()), png.size());
out.close();
std::cout << "✓ 已生成: " << fname << " (" << png.size() << " bytes)" << std::endl;
} catch (const std::exception& e) {
std::cerr << "✗ 错误: " << e.what() << std::endl;
return 1;
}
return 0;
}