软件图标支持上传图片:存 OSS/本机,media.php 代理读取

用户要求图标可以自己上传一张真实图片,而不是只能填一个字符。

设计:字符和图片并存,图片优先
- software 加 icon_key(图片对象键),icon(字符)保留作兜底
- 有图片用图片,删掉图片自动退回字符,任何时刻都不会没有图标
- 前端 badge 与邮件头部同一套判断,页面和邮件永远一致

存储与读取
- 图标走独立键空间 icons/YYYY/MM/<16hex>.<ext>
  不复用附件那套:附件必须挂在 thread 下才给读,图标不属于任何帖子
- 写:仅管理员(后台动作链)+ finfo 真实类型校验 + 尺寸下限 + 2MB 上限
- 读:media.php 对 icons/ 只校验键形状与文件存在(图标本就公开),
  写入侧仍然严格,不会让本站变成能读整个桶的开放代理
- 优先用 media.php?k= 代理;OSS 下顺便借图片处理缩放,省下行

顺手修掉两个真实安全问题
1. fb_icon_key_ok 原来用 [a-z0-9]{2,5} 匹配扩展名,.php 正好能过
   (3 个字母)—— 测试反向验证时发现,已改为图片扩展名白名单
2. fb_upload_key_ok 有同样的问题,附件键也能构造出 .php
   虽然真上传还有 finfo 兜底,但键本身就不该长这样,一并收紧
3. fb_icon_delete 原来用 @ 吞掉错误,删除失败仍返回「已保存」,
   用户会以为旧图清了、实际留了个可访问的孤儿图。改为返回 bool 并如实提示

验证:键校验 7 类非法输入全拒;图标写入 OSS 成功、media.php 读回 200 image/png、
PNG 魔数正确、缩放生效;越权读取(穿越/.php/未入库附件/不存在)全部按预期拒绝;
表单 enctype/文件框/accept/预览渲染齐全;仿真 PHP 伪图与 SVG 均被拒;
16x16 小图被拒。
This commit is contained in:
2026-11-03 08:13:21 +08:00
parent 738c6c09ff
commit 835c8c05da
8 changed files with 377 additions and 21 deletions
+9 -3
View File
@@ -325,12 +325,18 @@ SQL
$pdo->exec("ALTER TABLE software ADD COLUMN color TEXT NOT NULL DEFAULT ''");
}
/* ---- 软件增加图标(多软件并存时靠「图标 + 名字 + 颜色」三重区分)----
* 存一个字符即可:优先 emoji(日历 📅 / 锁 🔐 / 密码 🔑),也接受单个汉字或字母。
* 不存图片路径,避免再管一套上传 / 存储 / 权限 —— 邮件里也用同一个字符渲染,
* 前端与邮件天然一致,不会出现「页面有图标、邮件没有」的割裂。 */
* icon 存一个字符(emoji / 汉字 / 字母)—— 轻量兜底,没有图片时用它;
* icon_key 存【上传图片的对象键】—— 有它就优先用图片。
* 图片走与附件相同的存储与代理(local/... 或 <prefix>/...),
* 前端只输出 media.php?k=... 代理地址,不暴露真实位置。
* 两者并存:先有字符兜底,用户上传图片后自动切到图片,删掉图片又退回字符,
* 任何时刻都不会出现「没有图标」。 */
if (!isset($swNames['icon'])) {
$pdo->exec("ALTER TABLE software ADD COLUMN icon TEXT NOT NULL DEFAULT ''");
}
if (!isset($swNames['icon_key'])) {
$pdo->exec("ALTER TABLE software ADD COLUMN icon_key TEXT NOT NULL DEFAULT ''");
}
/* ---- 软件增加下载地址(状态「已解决」的邮件里给用户一个去下新版的入口)---- */
if (!isset($swNames['download_url'])) {
$pdo->exec("ALTER TABLE software ADD COLUMN download_url TEXT NOT NULL DEFAULT ''");