fix: 安全漏洞修复 — CLI密码泄露 + margin上限 + 路径检查 + CSP/限速
CRITICAL: - CLI --password 未提供时自动从 QRGEN_WIFI_PASSWORD 环境变量读取 避免密码出现在 ps aux / /proc/pid/cmdline 进程列表中 HIGH: - unsafe from_utf8_unchecked SAFETY 注释增强, 详细解释为何有效 - CLI --margin 添加 value_parser range(0..=100), 防止 u8=255 导致内存爆炸 - Web CORS permissive 添加注释说明公开 API 设计理由 - GUI FS 权限收紧: 写权限从 /c/Users/33644/** 限制为 Downloads/Documents/Desktop/AppData MEDIUM: - 新增 QrError::InvalidEcLevel(String), 不再复用 InvalidVersion(0) - --logo/--batch/--output_dir 均添加 check_path() 路径遍历检查 - Web 添加 tower::limit::ConcurrencyLimitLayer(10) 并发限制防 CPU 耗尽 - decode_image 添加 4096x4096 图片尺寸上限防解压炸弹 LOW: - Web 添加 Content-Security-Policy 响应头
This commit is contained in:
@@ -3,13 +3,24 @@
|
||||
//! 提供多遍自适应二值化策略和灰度图加载 API。
|
||||
|
||||
pub use image::GrayImage;
|
||||
use image::GenericImageView;
|
||||
|
||||
/// 最大图片尺寸(宽或高),防止解压炸弹(压缩 1MB→解压 10000×10000 像素)
|
||||
const MAX_IMAGE_DIMENSION: u32 = 4096;
|
||||
|
||||
/// 从图像字节加载为灰度图(公共 API,供调用方自定义预处理)
|
||||
///
|
||||
/// 支持 PNG/JPEG/WebP/BMP 等常见格式。
|
||||
/// 支持 PNG/JPEG/WebP/BMP 等常见格式。限制最大尺寸 4096×4096 防止资源耗尽。
|
||||
pub fn load_gray(bytes: &[u8]) -> Result<GrayImage, crate::error::QrError> {
|
||||
let img = image::load_from_memory(bytes)
|
||||
.map_err(|e| crate::error::QrError::DecodeFail(format!("图像解码失败: {e}")))?;
|
||||
let (w, h) = img.dimensions();
|
||||
if w > MAX_IMAGE_DIMENSION || h > MAX_IMAGE_DIMENSION {
|
||||
return Err(crate::error::QrError::DecodeFail(format!(
|
||||
"图片过大 ({}×{}),最大支持 {}×{}",
|
||||
w, h, MAX_IMAGE_DIMENSION, MAX_IMAGE_DIMENSION
|
||||
)));
|
||||
}
|
||||
Ok(img.to_luma8())
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ pub enum QrError {
|
||||
EmptyInput,
|
||||
/// 无效版本号 (1-40)
|
||||
InvalidVersion(u8),
|
||||
/// 无效纠错级别
|
||||
InvalidEcLevel(String),
|
||||
/// 数据过长,超出 QR 码最大容量
|
||||
DataTooLong,
|
||||
/// 解码失败
|
||||
@@ -30,6 +32,7 @@ impl fmt::Display for QrError {
|
||||
match self {
|
||||
QrError::EmptyInput => write!(f, "输入为空"),
|
||||
QrError::InvalidVersion(v) => write!(f, "无效版本号 (1-40): {v}"),
|
||||
QrError::InvalidEcLevel(s) => write!(f, "无效纠错级别: '{s}',支持 L/M/Q/H"),
|
||||
QrError::DataTooLong => write!(f, "数据过长,超出 QR 码最大容量"),
|
||||
QrError::DecodeFail(msg) => write!(f, "解码失败: {msg}"),
|
||||
QrError::FormatCorrupted(msg) => write!(f, "格式信息损坏: {msg}"),
|
||||
|
||||
+6
-1
@@ -324,7 +324,12 @@ pub(crate) fn parse_hex_color(s: &str) -> Result<[u8; 3], QrError> {
|
||||
match hex.len() {
|
||||
3 => {
|
||||
// #RGB → 每个分量乘以 17 扩展为 #RRGGBB(避免 String 分配)
|
||||
// SAFETY: hex 来自 `s[1..]`,已通过 starts_with('#') 检查,且只含 ASCII hex 字符
|
||||
// SAFETY: hex 来自 `s[1..]`,s 本身是 &str(由函数参数保证),
|
||||
// 因此 s 的每个字节都是有效 UTF-8。bytes[0]/[1]/[2] 取自 hex,
|
||||
// 是有效 UTF-8 单字节 ASCII hex 字符。将同一有效 ASCII 字节
|
||||
// 重复两次构成长度为 2 的字节序列,该序列必然也是有效 UTF-8。
|
||||
// 这是因为:ASCII 单字节字符 (0x00-0x7F) 重复任意次,每个字节
|
||||
// 都独立是有效的 UTF-8 序列,不依赖相邻字节。
|
||||
let bytes = hex.as_bytes();
|
||||
let r = u8::from_str_radix(unsafe {
|
||||
std::str::from_utf8_unchecked(&[bytes[0], bytes[0]])
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ impl FromStr for EcLevel {
|
||||
"M" => Ok(EcLevel::M),
|
||||
"Q" => Ok(EcLevel::Q),
|
||||
"H" => Ok(EcLevel::H),
|
||||
_ => Err(QrError::InvalidVersion(0)), // 复用变体
|
||||
_ => Err(QrError::InvalidEcLevel(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user